plotwidget.cpp
Go to the documentation of this file.
1 #include <QAction>
2 #include <QActionGroup>
3 #include <QApplication>
4 #include <QDebug>
5 #include <QDrag>
6 #include <QDragEnterEvent>
7 #include <QDragMoveEvent>
8 #include <QFileDialog>
9 #include <QMessageBox>
10 #include <QMenu>
11 #include <QMimeData>
12 #include <QPushButton>
13 #include <QWheelEvent>
14 #include <QSettings>
15 #include <iostream>
16 #include <limits>
17 #include <set>
18 #include <memory>
19 #include <QtXml/QDomElement>
20 #include "qwt_scale_widget.h"
21 #include "qwt_plot_canvas.h"
22 #include "qwt_scale_engine.h"
23 #include "qwt_plot_layout.h"
24 #include "qwt_scale_draw.h"
25 #include "qwt_text.h"
26 #include "plotwidget.h"
27 #include "removecurvedialog.h"
28 #include "curvecolorpick.h"
29 #include "qwt_plot_renderer.h"
30 #include "qwt_series_data.h"
31 #include "qwt_date_scale_draw.h"
33 #include "point_series_xy.h"
36 
38 {
39  virtual QwtText label(double v) const
40  {
41  QDateTime dt = QDateTime::fromMSecsSinceEpoch((qint64)(v*1000));
42  if( dt.date().year() == 1970 && dt.date().month() == 1 && dt.date().day() == 1)
43  {
44  return dt.toString("hh:mm:ss.z");
45  }
46  return dt.toString("hh:mm:ss.z\nyyyy MMM dd");
47 
48  }
49 };
50 
51 const double MAX_DOUBLE = std::numeric_limits<double>::max() / 2 ;
52 
53 static const char* noTransform = "noTransform";
54 static const char* Derivative1st = "1st Derivative";
55 static const char* Derivative2nd = "2nd Derivative";
56 static bool if_xy_plot_failed_show_dialog = true;
57 
58 static QStringList builtin_trans = {
62 };
63 
64 PlotWidget::PlotWidget(PlotDataMapRef &datamap, QWidget *parent):
65  QwtPlot(parent),
66  _zoomer( nullptr ),
67  _magnifier( nullptr ),
68  _panner1( nullptr ),
69  _panner2( nullptr ),
70  _tracker ( nullptr ),
71  _legend( nullptr ),
72  _grid( nullptr ),
73  _mapped_data( datamap ),
74  _dragging( { DragInfo::NONE, {}, nullptr } ),
76  _time_offset(0.0),
77  _axisX(nullptr),
78  _transform_select_dialog(nullptr),
79  _use_date_time_scale(false),
80  _zoom_enabled(true),
81  _keep_aspect_ratio(true)
82 {
83  _default_transform = "noTransform";
84  connect(this, &PlotWidget::curveListChanged, this, [this](){ this->updateMaximumZoomArea(); } );
85 
86  this->setAcceptDrops( true );
87 
88  this->setMinimumWidth( 100 );
89  this->setMinimumHeight( 100 );
90 
91  this->sizePolicy().setHorizontalPolicy( QSizePolicy::Expanding);
92  this->sizePolicy().setVerticalPolicy( QSizePolicy::Expanding);
93 
94  QwtPlotCanvas *canvas = new QwtPlotCanvas(this);
95 
96  canvas->setFrameStyle( QFrame::NoFrame );
98 
99  this->setCanvas( canvas );
100  this->setCanvasBackground( Qt::white );
101 
102  this->setAxisAutoScale(QwtPlot::yLeft, true);
103  this->setAxisAutoScale(QwtPlot::xBottom, true);
104 
106  this->plotLayout()->setAlignCanvasToScales( true );
107 
108  //--------------------------
109  _grid = new QwtPlotGrid();
110  _zoomer = ( new PlotZoomer( this->canvas() ) );
111  _magnifier = ( new PlotMagnifier( this->canvas() ) );
112  _panner1 = ( new QwtPlotPanner( this->canvas() ) );
113  _panner2 = ( new QwtPlotPanner( this->canvas() ) );
114  _tracker = ( new CurveTracker( this ) );
115 
116  _grid->setPen(QPen(Qt::gray, 0.0, Qt::DotLine));
117 
118  _zoomer->setRubberBandPen( QColor( Qt::red , 1, Qt::DotLine) );
119  _zoomer->setTrackerPen( QColor( Qt::green, 1, Qt::DotLine ) );
120  _zoomer->setMousePattern( QwtEventPattern::MouseSelect1, Qt::LeftButton, Qt::NoModifier );
122 
123  _magnifier->setAxisEnabled(xTop, false);
125 
126  _magnifier->setZoomInKey( Qt::Key_Plus, Qt::ControlModifier);
127  _magnifier->setZoomOutKey( Qt::Key_Minus, Qt::ControlModifier);
128 
129  // disable right button. keep mouse wheel
130  _magnifier->setMouseButton( Qt::NoButton );
131  connect(_magnifier, &PlotMagnifier::rescaled, this, [this](QRectF rect)
132  {
133  on_externallyResized(rect);
134  replot();
135  });
136 
137  _panner1->setMouseButton( Qt::LeftButton, Qt::ControlModifier);
138  _panner2->setMouseButton( Qt::MiddleButton, Qt::NoModifier);
139 
142 
143  //-------------------------
144 
145  buildActions();
146  buildLegend();
147 
148  this->canvas()->setMouseTracking(true);
149 
151 
153 
156 
157  QwtScaleWidget *bottomAxis = this->axisWidget(xBottom);
158  QwtScaleWidget *leftAxis = this->axisWidget(yLeft);
159 
160  bottomAxis->installEventFilter(this);
161  leftAxis->installEventFilter(this);
162 }
163 
165 {
166 
167  QIcon iconDeleteList;
168 
169  auto getActionAndIcon = [this](const char* text, const char* file)
170  {
171  QIcon icon;
172  icon.addFile( tr(file), QSize(30,30));
173  auto action = new QAction( tr(text), this);
174  action->setIcon(icon);
175  return action;
176  };
177 
178  _action_removeCurve = getActionAndIcon("&Remove curves",
179  ":/icons/resources/light/remove_list.png" );
180  _action_removeCurve->setStatusTip(tr("Remove one or more curves from this plot"));
181  connect(_action_removeCurve, &QAction::triggered, this, &PlotWidget::launchRemoveCurveDialog);
182 
183  _action_removeAllCurves = getActionAndIcon("&Remove ALL curves",
184  ":/icons/resources/light/remove.png" );
185  connect(_action_removeAllCurves, &QAction::triggered, this, &PlotWidget::detachAllCurves);
186  connect(_action_removeAllCurves, &QAction::triggered, this, &PlotWidget::undoableChange );
187 
188  _action_changeColorsDialog = getActionAndIcon("&Change colors",
189  ":/icons/resources/light/colored_charts.png" );
190  _action_changeColorsDialog->setStatusTip(tr("Change the color of the curves"));
191  connect(_action_changeColorsDialog, &QAction::triggered, this, &PlotWidget::on_changeColorsDialog_triggered);
192 
193  _action_showPoints = getActionAndIcon("&Show lines and/or points",
194  ":/icons/resources/light/point_chart.png" );
195  connect(_action_showPoints, &QAction::triggered, this, &PlotWidget::on_showPoints_triggered);
196 
197  _action_editLimits = new QAction(tr("&Edit Axis Limits"), this);
198  connect(_action_editLimits, &QAction::triggered, this, &PlotWidget::on_editAxisLimits_triggered);
199 
200  _action_zoomOutMaximum = getActionAndIcon("&Zoom Out", ":/icons/resources/light/zoom_max.png" );
201  connect(_action_zoomOutMaximum, &QAction::triggered, this, [this]()
202  {
203  zoomOut(true);
204  replot();
205  emit undoableChange();
206  });
207 
208  _action_zoomOutHorizontally = getActionAndIcon("&Zoom Out Horizontally",
209  ":/icons/resources/light/zoom_horizontal.png" );
210  connect(_action_zoomOutHorizontally, &QAction::triggered, this, [this]()
211  {
213  replot();
214  emit undoableChange();
215  });
216 
217  _action_zoomOutVertically = getActionAndIcon("&Zoom Out Vertically",
218  ":/icons/resources/light/zoom_vertical.png" );
219  connect(_action_zoomOutVertically, &QAction::triggered, this, [this]()
220  {
222  replot();
223  emit undoableChange();
224  });
225 
226  QFont font;
227  font.setPointSize(10);
228 
229  _action_noTransform = new QAction(tr("&NO Transform"), this);
230  _action_noTransform->setCheckable( true );
231  _action_noTransform->setChecked( true );
232  connect(_action_noTransform, &QAction::triggered, this, [this, font]()
233  {
234  QwtText text("");
235  text.setFont(font);
236  this->setFooter(text);
238  } );
239 
240  _action_1stDerivativeTransform = new QAction(tr("&1st Derivative"), this);
241  _action_1stDerivativeTransform->setCheckable( true );
242  connect(_action_1stDerivativeTransform, &QAction::triggered, this, [this, font]()
243  {
244  QwtText text("1st Derivative");
245  text.setFont(font);
246  this->setFooter(text);
248  } );
249 
250  _action_2ndDerivativeTransform = new QAction(tr("&2nd Derivative"), this);
251  _action_2ndDerivativeTransform->setCheckable( true );
252  connect(_action_2ndDerivativeTransform, &QAction::triggered, this, [this, font]()
253  {
254  QwtText text("2nd Derivative");
255  text.setFont(font);
256  this->setFooter(text);
258  } );
259 
260  _action_phaseXY = new QAction(tr("&XY plot"), this);
261  _action_phaseXY->setCheckable( true );
262 
263  _action_phaseXY->setEnabled(false);
264 
265  connect(_action_phaseXY, &QAction::triggered, this, &PlotWidget::on_convertToXY_triggered);
266 
267  _action_custom_transform = new QAction(tr("&Custom..."), this);
268  _action_custom_transform->setCheckable( true );
269  connect(_action_custom_transform, &QAction::triggered,
271 
272  _action_saveToFile = getActionAndIcon("&Save plot to file",
273  ":/icons/resources/light/save.png" );
274  connect(_action_saveToFile, &QAction::triggered, this, &PlotWidget::on_savePlotToFile);
275 
276  auto transform_group = new QActionGroup(this);
277 
278  transform_group->addAction(_action_noTransform);
279  transform_group->addAction(_action_1stDerivativeTransform);
280  transform_group->addAction(_action_2ndDerivativeTransform);
281  transform_group->addAction(_action_phaseXY);
282  transform_group->addAction(_action_custom_transform);
283 }
284 
285 
287 {
288  QString edit("&Edit Axis Limits ");
289  edit.append( _axis_limits_dialog->limitsEnabled() ? tr("(ENABLED)") : tr("(disabled)") ) ;
290  _action_editLimits->setText( edit );
291 
292  QMenu menu(this);
293  menu.addAction(_action_removeCurve);
294  menu.addAction(_action_removeAllCurves);
295  menu.addSeparator();
296  menu.addAction(_action_changeColorsDialog);
297  menu.addAction(_action_showPoints);
298  menu.addSeparator();
299  menu.addAction(_action_editLimits);
300  menu.addAction(_action_zoomOutMaximum);
301  menu.addAction(_action_zoomOutHorizontally);
302  menu.addAction(_action_zoomOutVertically);
303  menu.addSeparator();
304  menu.addAction( _action_noTransform );
305  menu.addAction( _action_1stDerivativeTransform );
306  menu.addAction( _action_2ndDerivativeTransform );
307  menu.addAction( _action_phaseXY );
308  menu.addAction( _action_custom_transform );
309  menu.addSeparator();
310  menu.addAction( _action_saveToFile );
311 
312  _action_removeCurve->setEnabled( ! _curve_list.empty() );
313  _action_removeAllCurves->setEnabled( ! _curve_list.empty() );
314  _action_changeColorsDialog->setEnabled( ! _curve_list.empty() );
315  _action_phaseXY->setEnabled( _axisX != nullptr );
316 
317  if( !_axisX )
318  {
319  menu.setToolTipsVisible(true);
320  _action_phaseXY->setToolTip(
321  "To show a XY plot, you must first provide the X axis.\n"
322  "Drag andn drop a curve using the RIGHT mouse\n"
323  "button instead of the left one." );
324  }
325 
326  menu.exec( canvas()->mapToGlobal(pos) );
327 }
328 
329 
331 {
332  _legend = new PlotLegend(this);
333 
334  _legend->attach( this );
335 
337  QColor color( Qt::black );
338  _legend->setTextPen( color );
339  _legend->setBorderPen( color );
340  QColor c( Qt::white );
341  c.setAlpha( 200 );
343 
344  _legend->setMaxColumns( 1 );
345  _legend->setAlignment( Qt::Alignment( Qt::AlignTop | Qt::AlignRight ) );
346  _legend->setBackgroundMode( QwtPlotLegendItem::BackgroundMode::LegendBackground );
347 
348  _legend->setBorderRadius( 4 );
349  _legend->setMargin( 1 );
350  _legend->setSpacing( 1 );
351  _legend->setItemMargin( 1 );
352 
353  QFont font = _legend->font();
354  font.setPointSize( 9 );
355  _legend->setFont( font );
356  _legend->setVisible( true );
357 }
358 
359 
360 
362 {
363 
364 }
365 
366 bool PlotWidget::addCurve(const std::string &name)
367 {
368  auto it = _mapped_data.numeric.find( name );
369  if( it == _mapped_data.numeric.end())
370  {
371  return false;
372  }
373 
374  if( _curve_list.find(name) != _curve_list.end())
375  {
376  return false;
377  }
378 
379  PlotData& data = it->second;
380  const auto qname = QString::fromStdString( name );
381 
382  auto curve = new QwtPlotCurve( qname );
383  try {
384  auto plot_qwt = createSeriesData( _default_transform, &data );
385  _curves_transform.insert( {name, _default_transform} );
386 
387  curve->setPaintAttribute( QwtPlotCurve::ClipPolygons, true );
388  curve->setPaintAttribute( QwtPlotCurve::FilterPointsAggressive, true );
389  curve->setData( plot_qwt );
390  }
391  catch( std::exception& ex)
392  {
393  QMessageBox::warning(this, "Exception!", ex.what());
394  return false;
395  }
396 
397  curve->setStyle( _curve_style );
398 
399  QColor color = data.getColorHint();
400  if( color == Qt::black)
401  {
402  color = randomColorHint();
403  data.setColorHint(color);
404  }
405  curve->setPen( color, (_curve_style == QwtPlotCurve::Dots) ? 4 : 0.8 );
406  curve->setRenderHint( QwtPlotItem::RenderAntialiased, true );
407 
408  curve->attach( this );
409  _curve_list.insert( std::make_pair(name, curve));
410 
411  auto marker = new QwtPlotMarker;
412  _point_marker.insert( std::make_pair(name, marker) );
413  marker->attach( this );
414  marker->setVisible( isXYPlot() );
415 
416  QwtSymbol *sym = new QwtSymbol(
418  Qt::red, color,
419  QSize(10,10));
420 
421  marker->setSymbol(sym);
422 
423  return true;
424 }
425 
426 void PlotWidget::removeCurve(const std::string &curve_name)
427 {
428  auto it = _curve_list.find(curve_name);
429  if( it != _curve_list.end() )
430  {
431  auto& curve = it->second;
432  curve->detach();
433  _curve_list.erase( it );
434 
435  auto marker_it = _point_marker.find(curve_name);
436  if( marker_it != _point_marker.end() )
437  {
438  auto marker = marker_it->second;
439  if( marker ){
440  marker->detach();
441  }
442  _point_marker.erase(marker_it);
443  }
444  _tracker->redraw();
445  emit curveListChanged();
446  }
447  _curves_transform.erase( curve_name );
448 
449  if( isXYPlot() && _axisX && _axisX->name() == curve_name)
450  {
451  // Without the X axis, transform all the curves to noTransform
452  _axisX = nullptr;
453  _default_transform.clear();
454  for(auto& it : _curve_list)
455  {
456  auto& curve = it.second;
457 
458  auto data_it = _mapped_data.numeric.find( curve_name );
459  if( data_it != _mapped_data.numeric.end())
460  {
461  const auto& data = data_it->second;
462  auto data_series = createSeriesData( _default_transform, &data);
463  curve->setData( data_series );
464  }
465  }
467 
468  _tracker->redraw();
469  emit curveListChanged();
470  }
471 }
472 
474 {
475  return _curve_list.empty();
476 }
477 
478 const std::map<std::string, QwtPlotCurve*> &PlotWidget::curveList() const
479 {
480  return _curve_list;
481 }
482 
483 void PlotWidget::dragEnterEvent(QDragEnterEvent *event)
484 {
485  changeBackgroundColor( QColor( 230, 230, 230 ) );
486 
487  const QMimeData *mimeData = event->mimeData();
488  QStringList mimeFormats = mimeData->formats();
489  _dragging.curves.clear();
490  _dragging.source = event->source();
491  for(const QString& format: mimeFormats)
492  {
493  QByteArray encoded = mimeData->data( format );
494  QDataStream stream(&encoded, QIODevice::ReadOnly);
495 
496  while (!stream.atEnd())
497  {
498  QString curve_name;
499  stream >> curve_name;
500  if(!curve_name.isEmpty()) {
501  _dragging.curves.push_back(curve_name);
502  }
503  }
504 
505  if( format.contains( "curveslist/add_curve") )
506  {
508  event->acceptProposedAction();
509  }
510  if( format.contains( "curveslist/new_X_axis") && _dragging.curves.size() == 1 )
511  {
513  event->acceptProposedAction();
514  }
515  if( format.contains( "plot_area") )
516  {
517  if(_dragging.curves.size() == 1 &&
518  windowTitle() != _dragging.curves.front() )
519  {
521  event->acceptProposedAction();
522  }
523  }
524  }
525 }
526 
527 
528 void PlotWidget::dragLeaveEvent(QDragLeaveEvent*)
529 {
530  QPoint local_pos = canvas()->mapFromGlobal(QCursor::pos()) ;
531  // prevent spurious exits
532  if( canvas()->rect().contains( local_pos ))
533  {
534  // changeBackgroundColor( QColor( 250, 150, 150 ) );
535  }
536  else{
537  changeBackgroundColor( Qt::white );
539  _dragging.curves.clear();
540  }
541 }
542 
543 void PlotWidget::dropEvent(QDropEvent *)
544 {
545  bool curves_changed = false;
546  bool background_changed = false;
547 
549  {
550  for( const auto& curve_name : _dragging.curves)
551  {
552  bool added = addCurve( curve_name.toStdString() );
553  curves_changed = curves_changed || added;
554  }
555  emit curvesDropped();
556  }
557  else if( _dragging.mode == DragInfo::NEW_X)
558  {
559  changeAxisX( _dragging.curves.front() );
560  curves_changed = true;
561  emit curvesDropped();
562  }
563  else if( _dragging.mode == DragInfo::SWAP_PLOTS )
564  {
565  auto plot_widget = dynamic_cast<PlotWidget*>(_dragging.source);
566  if( plot_widget )
567  {
568  emit swapWidgetsRequested( plot_widget, this );
569  }
570  }
571  if( _dragging.mode != DragInfo::NONE &&
572  canvasBackground().color() != Qt::white)
573  {
574  this->setCanvasBackground( Qt::white );
575  background_changed = true;
576  }
577 
578  if( curves_changed )
579  {
580  emit curveListChanged();
581  zoomOut(false);
582  emit undoableChange();
583  }
584  if( curves_changed || background_changed )
585  {
586  _tracker->redraw();
587  replot();
588  }
590  _dragging.curves.clear();
591 }
592 
594 {
595  for(auto& it: _curve_list) { it.second->detach(); }
596  for(auto& it: _point_marker) { it.second->detach(); }
597 
598  if( isXYPlot() )
599  {
600  _axisX = nullptr;
601  _action_noTransform->trigger();
602  }
603  _curve_list.clear();
604  _curves_transform.clear();
605  _point_marker.clear();
606 
607  _tracker->redraw();
608 
609  emit curveListChanged();
610 
611  replot();
612 }
613 
614 void PlotWidget::on_panned(int , int )
615 {
617 }
618 
619 QDomElement PlotWidget::xmlSaveState( QDomDocument &doc) const
620 {
621  QDomElement plot_el = doc.createElement("plot");
622 
623  QDomElement range_el = doc.createElement("range");
624  QRectF rect = this->canvasBoundingRect();
625  range_el.setAttribute("bottom", QString::number(rect.bottom(), 'f', 6) );
626  range_el.setAttribute("top", QString::number(rect.top(), 'f', 6));
627  range_el.setAttribute("left", QString::number(rect.left(), 'f', 6));
628  range_el.setAttribute("right", QString::number(rect.right() ,'f', 6));
629  plot_el.appendChild(range_el);
630 
631  QDomElement limitY_el = doc.createElement("limitY");
633  limitY_el.setAttribute("min", QString::number( _custom_Y_limits.min) );
634  }
636  limitY_el.setAttribute("max", QString::number( _custom_Y_limits.max) );
637  }
638  plot_el.appendChild(limitY_el);
639 
640  for(auto& it: _curve_list)
641  {
642  auto& name = it.first;
643  auto& curve = it.second;
644  QDomElement curve_el = doc.createElement("curve");
645  curve_el.setAttribute( "name", QString::fromStdString( name ));
646  curve_el.setAttribute( "R", curve->pen().color().red());
647  curve_el.setAttribute( "G", curve->pen().color().green());
648  curve_el.setAttribute( "B", curve->pen().color().blue());
649  curve_el.setAttribute( "custom_transform", _curves_transform.at(name) );
650 
651  plot_el.appendChild(curve_el);
652  }
653 
654  QDomElement transform = doc.createElement("transform");
655 
656  if( _action_custom_transform->isChecked() )
657  {
658  transform.setAttribute("value", tr("Custom::") +_default_transform);
659  }
660  else{
661  transform.setAttribute("value", _default_transform);
662  }
663 
664  if( _axisX )
665  {
666  transform.setAttribute("axisX", QString::fromStdString( _axisX->name()) );
667  }
668 
669  plot_el.appendChild(transform);
670 
671  return plot_el;
672 }
673 
674 bool PlotWidget::xmlLoadState(QDomElement &plot_widget)
675 {
676  std::set<std::string> added_curve_names;
677 
678  QDomElement transform = plot_widget.firstChildElement( "transform" );
679 
680  QDomElement limitY_el = plot_widget.firstChildElement("limitY");
681  if( !limitY_el.isNull() )
682  {
683  if( limitY_el.hasAttribute("min") ) {
684  _custom_Y_limits.min = limitY_el.attribute("min").toDouble();
686  }
687  else{
690  }
691 
692  if( limitY_el.hasAttribute("max") ) {
693  _custom_Y_limits.max = limitY_el.attribute("max").toDouble();
695  }
696  else{
699  }
700  }
701 
702  static bool warning_message_shown = false;
703 
704  bool curve_added = false;
705 
706  for (QDomElement curve_element = plot_widget.firstChildElement( "curve" ) ;
707  !curve_element.isNull();
708  curve_element = curve_element.nextSiblingElement( "curve" ) )
709  {
710  std::string curve_name = curve_element.attribute("name").toStdString();
711  int R = curve_element.attribute("R").toInt();
712  int G = curve_element.attribute("G").toInt();
713  int B = curve_element.attribute("B").toInt();
714  QColor color(R,G,B);
715 
716  if( _mapped_data.numeric.find(curve_name) != _mapped_data.numeric.end() )
717  {
718  auto added = addCurve( curve_name );
719  curve_added = curve_added || added;
720  _curve_list[curve_name]->setPen( color, 1.0);
721  added_curve_names.insert(curve_name );
722  }
723  else if( ! warning_message_shown )
724  {
725  QMessageBox::warning(this, "Warning",
726  tr("Can't find one or more curves.\n"
727  "This message will be shown only once.") );
728  warning_message_shown = true;
729  }
730  }
731 
732  bool curve_removed = true;
733 
734  while( curve_removed )
735  {
736  curve_removed = false;
737  for(auto& it: _curve_list)
738  {
739  auto curve_name = it.first;
740  if( added_curve_names.find( curve_name ) == added_curve_names.end())
741  {
742  removeCurve( curve_name );
743  curve_removed = true;
744  break;
745  }
746  }
747  }
748 
749  if( !transform.isNull() )
750  {
751  QString trans_value = transform.attribute("value");
752  if( trans_value.isEmpty() || trans_value == "noTransform" )
753  {
754  _action_noTransform->trigger();
755  }
756  else if( trans_value == Derivative1st )
757  {
759  }
760  else if( trans_value == Derivative2nd )
761  {
763  }
764  else if( trans_value == "XYPlot" )
765  {
766  changeAxisX( transform.attribute("axisX") );
767  _action_phaseXY->trigger();
768  }
769  else if( trans_value.startsWith("Custom::" ) )
770  {
771  _default_transform = trans_value.remove(0, 8);
772 
774 
775  for (QDomElement curve_element = plot_widget.firstChildElement( "curve" ) ;
776  !curve_element.isNull();
777  curve_element = curve_element.nextSiblingElement( "curve" ) )
778  {
779  std::string curve_name = curve_element.attribute("name").toStdString();
780  auto custom_attribute = curve_element.attribute("custom_transform");
781  if( !custom_attribute.isNull() )
782  {
783  _curves_transform[curve_name] = custom_attribute;
784  }
785  }
787  _action_custom_transform->setChecked(true);
788  }
789  }
790 
791  if( curve_removed || curve_added)
792  {
793  _tracker->redraw();
794  replot();
795  emit curveListChanged();
796  }
797 
798  //-----------------------------------------
799 
800  QDomElement rectangle = plot_widget.firstChildElement( "range" );
801  if( isXYPlot() )
802  {
804  }
805 
806  if( !rectangle.isNull() )
807  {
808  QRectF rect;
809  rect.setBottom( rectangle.attribute("bottom").toDouble());
810  rect.setTop( rectangle.attribute("top").toDouble());
811  rect.setLeft( rectangle.attribute("left").toDouble());
812  rect.setRight( rectangle.attribute("right").toDouble());
813  this->setZoomRectangle( rect, false);
814  }
815 
816  replot();
817  return true;
818 }
819 
820 
822 {
823  QRectF rect;
824  rect.setBottom( this->canvasMap( yLeft ).s1() );
825  rect.setTop( this->canvasMap( yLeft ).s2() );
826  rect.setLeft( this->canvasMap( xBottom ).s1() );
827  rect.setRight( this->canvasMap( xBottom ).s2() );
828  return rect;
829 }
830 
832 {
833  QRectF max_rect ;
834  auto rangeX = getMaximumRangeX();
835  max_rect.setLeft( rangeX.min );
836  max_rect.setRight( rangeX.max );
837 
838  auto rangeY = getMaximumRangeY( rangeX );
839  max_rect.setBottom( rangeY.min );
840  max_rect.setTop( rangeY.max );
841 
842  if( isXYPlot() && _keep_aspect_ratio )
843  {
844  const QRectF canvas_rect = canvas()->contentsRect();
845  const double canvas_ratio = fabs( canvas_rect.width() / canvas_rect.height() );
846  const double data_ratio = fabs(max_rect.width() / max_rect.height());
847  if( data_ratio < canvas_ratio )
848  {
849  // height is negative!!!!
850  double new_width = fabs( max_rect.height() * canvas_ratio );
851  double increment = new_width - max_rect.width();
852  max_rect.setWidth( new_width );
853  max_rect.moveLeft( max_rect.left() - 0.5*increment );
854  }
855  else{
856  // height must be negative!!!!
857  double new_height = -(max_rect.width() / canvas_ratio);
858  double increment = fabs(new_height - max_rect.height());
859  max_rect.setHeight( new_height );
860  max_rect.moveTop( max_rect.top() + 0.5*increment );
861  }
862  _magnifier->setAxisLimits( xBottom, max_rect.left(), max_rect.right() );
863  _magnifier->setAxisLimits( yLeft, max_rect.bottom(), max_rect.top() );
864  _zoomer->keepAspectratio( true );
865  }
866  else{
867  _magnifier->setAxisLimits( xBottom, max_rect.left(), max_rect.right() );
868  _magnifier->setAxisLimits( yLeft, max_rect.bottom(), max_rect.top() );
869  _zoomer->keepAspectratio( false );
870  }
871  _max_zoom_rect = max_rect;
872 }
873 
875 {
876  const QwtScaleMap xMap = canvasMap( QwtPlot::xBottom );
877  const QwtScaleMap yMap = canvasMap( QwtPlot::yLeft );
878 
879  QRectF canvas_rect = canvas()->contentsRect();
880  canvas_rect = canvas_rect.normalized();
881  const double x1 = xMap.invTransform( canvas_rect.left() );
882  const double x2 = xMap.invTransform( canvas_rect.right() );
883  const double y1 = yMap.invTransform( canvas_rect.bottom() );
884  const double y2 = yMap.invTransform( canvas_rect.top() );
885 
886  const double data_ratio = ( x2 - x1 ) / ( y2 - y1 );
887  const double canvas_ratio = canvas_rect.width() / canvas_rect.height();
888  const double max_ratio = fabs( _max_zoom_rect.width() / _max_zoom_rect.height() );
889 
890  QRectF rect( QPointF(x1,y2), QPointF(x2,y1) );
891 
892  if( data_ratio < canvas_ratio )
893  {
894  double new_width = fabs( rect.height() * canvas_ratio );
895  double increment = new_width - rect.width();
896  rect.setWidth( new_width );
897  rect.moveLeft( rect.left() - 0.5*increment );
898  }
899  else{
900  double new_height = -(rect.width() / canvas_ratio);
901  double increment = fabs(new_height - rect.height());
902  rect.setHeight( new_height );
903  rect.moveTop( rect.top() + 0.5*increment );
904  }
905  if( rect.contains(_max_zoom_rect) )
906  {
907  rect = _max_zoom_rect;
908  }
909 
910  this->setAxisScale( yLeft,
911  std::min(rect.bottom(), rect.top() ),
912  std::max(rect.bottom(), rect.top() ));
913  this->setAxisScale( xBottom,
914  std::min( rect.left(), rect.right()),
915  std::max( rect.left(), rect.right()) );
916  this->updateAxes();
917 }
918 
919 void PlotWidget::resizeEvent( QResizeEvent *ev )
920 {
923 
924  if( isXYPlot() && _keep_aspect_ratio )
925  {
927  }
928 }
929 
931 {
933  // qDebug() << canvasBoundingRect();
934 }
935 
937 {
938  _keep_aspect_ratio = active;
939  if( isXYPlot() && active)
940  {
941  // TODo rescaler
942  _zoomer->keepAspectratio( true );
943  }
944  else{
945  _zoomer->keepAspectratio( false );
946  }
947 }
948 
949 void PlotWidget::setZoomRectangle(QRectF rect, bool emit_signal)
950 {
951  QRectF current_rect = canvasBoundingRect();
952  if( current_rect == rect)
953  {
954  return;
955  }
956  this->setAxisScale( yLeft,
957  std::min(rect.bottom(), rect.top() ),
958  std::max(rect.bottom(), rect.top() ));
959  this->setAxisScale( xBottom,
960  std::min( rect.left(), rect.right()),
961  std::max( rect.left(), rect.right()) );
962  this->updateAxes();
963 
964  if( isXYPlot() && _keep_aspect_ratio )
965  {
967  }
968 
969  if( emit_signal )
970  {
971  if( isXYPlot()) {
972  emit undoableChange();
973  }
974  else{
975  emit rectChanged(this, rect);
976  }
977  }
978 }
979 
981 {
982  if( isXYPlot() )
983  {
984  auto it = _mapped_data.numeric.find( _axisX->name() );
985  if( it != _mapped_data.numeric.end() ){
986  _axisX = &(it->second);
987  }
988  else{
989  _axisX = nullptr;
990  }
991  }
992 
993  for (auto& curve_it: _curve_list)
994  {
995  auto& curve = curve_it.second;
996  const auto& curve_name = curve_it.first;
997 
998  auto data_it = _mapped_data.numeric.find( curve_name );
999  if( data_it != _mapped_data.numeric.end())
1000  {
1001  const auto& data = data_it->second;
1002  const auto& transform = _curves_transform.at(curve_name);
1003  auto data_series = createSeriesData( transform, &data);
1004  curve->setData( data_series );
1005  }
1006  }
1007 
1008  if( _curve_list.size() == 0){
1009  setDefaultRangeX();
1010  }
1011 }
1012 
1013 void PlotWidget::activateLegend(bool activate)
1014 {
1015  _legend->setVisible(activate);
1016 }
1017 
1018 void PlotWidget::activateGrid(bool activate)
1019 {
1020  _grid->enableX(activate);
1021  _grid->enableXMin(activate);
1022  _grid->enableY(activate);
1023  _grid->enableYMin(activate);
1024  _grid->attach(this);
1025 }
1026 
1028 {
1029  _tracker->setParameter( val );
1030 }
1031 
1032 void PlotWidget::enableTracker(bool enable)
1033 {
1034  _tracker->setEnabled( enable && !isXYPlot() );
1035 }
1036 
1037 void PlotWidget::setTrackerPosition(double abs_time)
1038 {
1039  if( isXYPlot()){
1040  for (auto& it: _curve_list)
1041  {
1042  auto& name = it.first;
1043  auto series = static_cast<DataSeriesBase*>( it.second->data() );
1044  auto pointXY = series->sampleFromTime(abs_time);
1045  if( pointXY ){
1046  _point_marker[name]->setValue( pointXY.value() );
1047  }
1048  }
1049  }
1050  else{
1051  double relative_time = abs_time - _time_offset;
1052  _tracker->setPosition( QPointF( relative_time , 0.0) );
1053  }
1054 }
1055 
1057 {
1058  auto prev_offset = _time_offset;
1059  _time_offset = offset;
1060 
1061  if( fabs( prev_offset - offset) > std::numeric_limits<double>::epsilon() )
1062  {
1063  for(auto& it: _curve_list)
1064  {
1065  auto series = static_cast<DataSeriesBase*>( it.second->data() );
1066  series->setTimeOffset(_time_offset);
1067  }
1068  if( !isXYPlot() )
1069  {
1070  QRectF rect = canvasBoundingRect();
1071  double delta = prev_offset - offset;
1072  rect.moveLeft( rect.left() + delta );
1073  setZoomRectangle( rect, false );
1074  }
1075  }
1076 }
1077 
1079 {
1080  if( enable != _use_date_time_scale)
1081  {
1082  _use_date_time_scale = enable;
1083  if( enable )
1084  {
1086  }
1087  else{
1089  }
1090  }
1091 }
1092 
1093 
1094 PlotData::RangeTime PlotWidget::getMaximumRangeX() const
1095 {
1096  double left = std::numeric_limits<double>::max();
1097  double right = -std::numeric_limits<double>::max();
1098 
1099  for(auto& it: _curve_list)
1100  {
1101  auto series = static_cast<DataSeriesBase*>( it.second->data() );
1102  const auto max_range_X = series->getVisualizationRangeX();
1103  if( !max_range_X ) continue;
1104 
1105  left = std::min(max_range_X->min, left);
1106  right = std::max(max_range_X->max, right);
1107  }
1108 
1109  if( left > right ){
1110  left = 0;
1111  right = 0;
1112  }
1113 
1114  double margin = 0.0;
1115  if( fabs(right - left) > std::numeric_limits<double>::epsilon() )
1116  {
1117  margin = isXYPlot() ? ((right-left) * 0.025) : 0.0;
1118  }
1119  right = right + margin;
1120  left = left - margin;
1121 
1122  return PlotData::RangeTime( {left,right} );
1123 }
1124 
1125 //TODO report failure for empty dataset
1126 PlotData::RangeValue PlotWidget::getMaximumRangeY( PlotData::RangeTime range_X) const
1127 {
1128  double top = -std::numeric_limits<double>::max();
1129  double bottom = std::numeric_limits<double>::max();
1130 
1131  for(auto& it: _curve_list)
1132  {
1133  auto series = static_cast<DataSeriesBase*>( it.second->data() );
1134 
1135  const auto max_range_X = series->getVisualizationRangeX();
1136  if( !max_range_X ) continue;
1137 
1138  double left = std::max(max_range_X->min, range_X.min);
1139  double right = std::min(max_range_X->max, range_X.max);
1140 
1141  left += _time_offset;
1142  right += _time_offset;
1143  left = std::nextafter(left, right);
1144  right = std::nextafter(right, left);
1145 
1146  auto range_Y = series->getVisualizationRangeY( {left, right} );
1147  if( !range_Y )
1148  {
1149  qDebug() << " invalid range_Y in PlotWidget::maximumRangeY";
1150  continue;
1151  }
1152  if( top < range_Y->max ) top = range_Y->max;
1153  if( bottom > range_Y->min ) bottom = range_Y->min;
1154  }
1155 
1156  double margin = 0.1;
1157 
1158  if( bottom > top ){
1159  bottom = 0;
1160  top = 0;
1161  }
1162 
1163  if( top - bottom > std::numeric_limits<double>::epsilon() )
1164  {
1165  margin = (top-bottom) * 0.025;
1166  }
1167 
1168  const bool lower_limit = _custom_Y_limits.min > -MAX_DOUBLE;
1169  const bool upper_limit = _custom_Y_limits.max < MAX_DOUBLE;
1170 
1171  if(lower_limit)
1172  {
1173  bottom = _custom_Y_limits.min;
1174  if( top < bottom ) top = bottom + margin;
1175  }
1176 
1177  if( upper_limit )
1178  {
1179  top = _custom_Y_limits.max;
1180  if( top < bottom ) bottom = top - margin;
1181  }
1182 
1183  if( !lower_limit && !upper_limit )
1184  {
1185  top += margin;
1186  bottom -= margin;
1187  }
1188 
1189  return PlotData::RangeValue({ bottom, top});
1190 }
1191 
1192 
1194 {
1195  for(auto& it: _curve_list)
1196  {
1197  auto series = static_cast<DataSeriesBase*>( it.second->data() );
1198  bool res = series->updateCache();
1199  //TODO check res and do something if false.
1200  }
1201 }
1202 
1204 {
1205  RemoveCurveDialog* dialog = new RemoveCurveDialog(this);
1206  auto prev_curve_count = _curve_list.size();
1207 
1208  for(auto& it: _curve_list)
1209  {
1210  dialog->addCurveName( QString::fromStdString( it.first ),
1211  it.second->pen().color() );
1212  }
1213 
1214  dialog->exec();
1215 
1216  if( prev_curve_count != _curve_list.size() )
1217  {
1218  emit undoableChange();
1219  }
1220 }
1221 
1223 {
1224  std::map<std::string,QColor> color_by_name;
1225 
1226  for(auto& it: _curve_list)
1227  {
1228  const auto& curve_name = it.first;
1229  auto& curve = it.second;
1230  color_by_name.insert(std::make_pair( curve_name, curve->pen().color() ));
1231  }
1232 
1233  CurveColorPick* dialog = new CurveColorPick(color_by_name, this);
1234 
1235  connect( dialog, &CurveColorPick::changeColor, this, &PlotWidget::on_changeColor,
1236  Qt::DirectConnection);
1237 
1238  dialog->exec();
1239 
1240  if( dialog->anyColorModified() )
1241  {
1242  emit undoableChange();
1243  }
1244 }
1245 
1246 void PlotWidget::on_changeColor(QString curve_name, QColor new_color)
1247 {
1248  auto it = _curve_list.find(curve_name.toStdString());
1249  if( it != _curve_list.end())
1250  {
1251  auto& curve = it->second;
1252  if( curve->pen().color() != new_color)
1253  {
1254  curve->setPen( new_color, 1.0 );
1255  }
1256  replot();
1257  }
1258 }
1259 
1261 {
1263  {
1265  }
1267  {
1269  }
1270  else if( _curve_style == QwtPlotCurve::Dots )
1271  {
1273  }
1274 
1275  for(auto& it: _curve_list)
1276  {
1277  auto& curve = it.second;
1278  curve->setPen( curve->pen().color(), (_curve_style == QwtPlotCurve::Dots) ? 4 : 0.8 );
1279  curve->setStyle( _curve_style );
1280  }
1281  replot();
1282 }
1283 
1284 void PlotWidget::on_externallyResized(const QRectF& rect)
1285 {
1286  QRectF current_rect = canvasBoundingRect();
1287  if( current_rect == rect)
1288  {
1289  return;
1290  }
1291 
1292  if( isXYPlot() )
1293  {
1294  emit undoableChange();
1295  }
1296  else{
1297  emit rectChanged(this, rect);
1298  }
1299 }
1300 
1301 
1302 void PlotWidget::zoomOut(bool emit_signal)
1303 {
1304  if( _curve_list.size() == 0)
1305  {
1306  QRectF rect(0, 1, 1, -1);
1307  this->setZoomRectangle(rect, false);
1308  return;
1309  }
1311  setZoomRectangle( _max_zoom_rect, emit_signal);
1312 }
1313 
1315 {
1317  QRectF act = canvasBoundingRect();
1318  auto rangeX = getMaximumRangeX();
1319 
1320  act.setLeft( rangeX.min );
1321  act.setRight( rangeX.max );
1322  this->setZoomRectangle(act, emit_signal);
1323 }
1324 
1326 {
1328  QRectF rect = canvasBoundingRect();
1329  auto rangeY = getMaximumRangeY( {rect.left(), rect.right()} );
1330 
1331  rect.setBottom( rangeY.min );
1332  rect.setTop( rangeY.max );
1333  this->setZoomRectangle(rect, emit_signal);
1334 }
1335 
1336 void PlotWidget::on_changeToBuiltinTransforms(QString new_transform )
1337 {
1338  if( _default_transform == new_transform)
1339  {
1340  return;
1341  }
1342  enableTracker(true);
1343 
1344  for(auto& it : _curve_list)
1345  {
1346  const auto& curve_name = it.first;
1347  auto& curve = it.second;
1348 
1349  _point_marker[ curve_name ]->setVisible(false);
1350  curve->setTitle( QString::fromStdString( curve_name ) );
1351  _curves_transform[curve_name] = new_transform;
1352 
1353  auto data_it = _mapped_data.numeric.find( curve_name );
1354  if( data_it != _mapped_data.numeric.end())
1355  {
1356  const auto& data = data_it->second;
1357  auto data_series = createSeriesData( new_transform, &data);
1358  curve->setData( data_series );
1359  }
1360  }
1361 
1362  _default_transform = new_transform;
1363  zoomOut(true);
1364  replot();
1365 }
1366 
1367 
1369 {
1370  return _axisX && _action_phaseXY->isChecked();
1371 }
1372 
1373 
1375 {
1376  if( !_axisX )
1377  {
1378  QMessageBox::warning(this, tr("Warning"),
1379  tr("To show a XY plot, you must first provide an alternative X axis.\n"
1380  "You can do this drag'n dropping a curve using the RIGHT mouse button "
1381  "instead of the left mouse button.") );
1382  _action_noTransform->trigger();
1383  return;
1384  }
1385 
1386  std::deque<PointSeriesXY*> xy_timeseries;
1387 
1388  try{
1389  for(auto& it: _curve_list)
1390  {
1391  const auto& curve_name = it.first;
1392  auto& curve = it.second;
1393  auto& data = _mapped_data.numeric.find(curve_name)->second;
1394  xy_timeseries.push_back( new PointSeriesXY( &data, _axisX) );
1395  _curves_transform[curve_name] = "XYPlot";
1396  }
1397  }
1398  catch(std::exception& ex)
1399  {
1400  QMessageBox::warning(this, tr("Error"), tr(ex.what()) );
1401  _action_noTransform->trigger();
1402  return;
1403  }
1404 
1405  enableTracker(false);
1406  _default_transform = "XYPlot";
1407 
1408  for(auto& it: _curve_list)
1409  {
1410  const auto& curve_name = it.first;
1411  auto& curve = it.second;
1412  curve->setData( xy_timeseries.front() );
1413  xy_timeseries.pop_front();
1414  _point_marker[ curve_name ]->setVisible(true);
1415  }
1416 
1417  QFont font_footer;
1418  font_footer.setPointSize(10);
1419  QwtText text( QString::fromStdString( _axisX->name()) );
1420  text.setFont(font_footer);
1421 
1422  this->setFooter( text );
1423 
1424  zoomOut(true);
1425  replot();
1426 }
1427 
1428 
1430 {
1431  QSettings settings;
1432  QByteArray xml_text = settings.value("AddCustomPlotDialog.savedXML",
1433  QByteArray() ).toByteArray();
1434  if( !xml_text.isEmpty() )
1435  {
1436  _snippets = GetSnippetsFromXML(xml_text);
1437  }
1438 }
1439 
1441 {
1442  std::string error_message;
1443 
1444  for (auto& curve_it: _curve_list)
1445  {
1446  auto& curve = curve_it.second;
1447  const auto& curve_name = curve_it.first;
1448  const auto& transform = _curves_transform.at(curve_name);
1449 
1450  auto data_it = _mapped_data.numeric.find( curve_name );
1451  if( data_it != _mapped_data.numeric.end())
1452  {
1453  auto& data = data_it->second;
1454  try {
1455  auto data_series = createSeriesData( transform, &data);
1456  curve->setData( data_series );
1457 
1458  if( transform == noTransform || transform.isEmpty())
1459  {
1460  curve->setTitle( QString::fromStdString(curve_name) );
1461  }
1462  else{
1463  curve->setTitle( QString::fromStdString(curve_name) + tr(" [") + transform + tr("]") );
1464  }
1465  }
1466  catch (...)
1467  {
1468  _curves_transform[curve_name] = noTransform;
1469  auto data_series = createSeriesData( noTransform, &data);
1470  curve->setData( data_series );
1471 
1472  error_message += curve_name + (" [") + transform.toStdString() + ("]\n");
1473 
1474  curve->setTitle( QString::fromStdString(curve_name) );
1475  }
1476  }
1477  }
1478  if( error_message.size() > 0)
1479  {
1480  QMessageBox msgBox(this);
1481  msgBox.setWindowTitle("Warnings");
1482  msgBox.setText(tr("Something went wront while creating the following curves. "
1483  "Please check that the transform equation is correct.\n\n") +
1484  QString::fromStdString(error_message) );
1485  msgBox.exec();
1486  }
1487 }
1488 
1490 {
1492 
1493  QStringList available_trans;
1494  for (const auto& it: _snippets)
1495  {
1496  bool valid = true;
1497  QStringList required_channels = CustomFunction::getChannelsFromFuntion( it.second.equation );
1498  for (const auto& channel: required_channels)
1499  {
1500  if( _mapped_data.numeric.count( channel.toStdString() ) == 0)
1501  {
1502  valid = false;
1503  break;
1504  }
1505  }
1506  valid = valid && it.second.equation.contains("value");
1507 
1508  if( valid )
1509  {
1510  available_trans.push_back( it.first );
1511  }
1512  }
1513 
1514  TransformSelector dialog( builtin_trans, available_trans,
1516  this);
1517 
1518  if (dialog.exec() == QDialog::Rejected )
1519  {
1520  return;
1521  }
1522 
1524  zoomOut(false);
1525  replot();
1526 }
1527 
1528 void PlotWidget::changeAxisX(QString curve_name)
1529 {
1530  auto it = _mapped_data.numeric.find( curve_name.toStdString() );
1531  if( it != _mapped_data.numeric.end())
1532  {
1533  _axisX = &(it->second);
1534  _action_phaseXY->trigger();
1535  }
1536  else{
1537  //TODO: do nothing (?)
1538  }
1539 }
1540 
1542 {
1543  QString fileName;
1544 
1545  QFileDialog saveDialog;
1546  saveDialog.setAcceptMode(QFileDialog::AcceptSave);
1547  saveDialog.setDefaultSuffix("png");
1548 
1549  saveDialog.setNameFilter("Compatible formats (*.jpg *.jpeg *.png)");
1550 
1551  saveDialog.exec();
1552 
1553  if(saveDialog.result() == QDialog::Accepted && !saveDialog.selectedFiles().empty())
1554  {
1555  fileName = saveDialog.selectedFiles().first();
1556 
1557  QPixmap pixmap (1200,900);
1558  QPainter * painter = new QPainter(&pixmap);
1559 
1560  if ( !fileName.isEmpty() )
1561  {
1562  QwtPlotRenderer rend;
1563  rend.render(this, painter, QRect(0, 0, pixmap.width(), pixmap.height()));
1564  pixmap.save(fileName);
1565  }
1566  }
1567 }
1568 
1570 {
1571  auto rangeX = this->getMaximumRangeX();
1572 
1573  //temporary reset the limit during editing
1576 
1577  auto rangeY = getMaximumRangeY(rangeX);
1578 
1580  _axis_limits_dialog->exec();
1581 
1583 
1585  replot();
1586  emit undoableChange();
1587 }
1588 
1589 
1590 bool PlotWidget::eventFilter(QObject *obj, QEvent *event)
1591 {
1592  QwtScaleWidget *bottomAxis = this->axisWidget(xBottom);
1593  QwtScaleWidget *leftAxis = this->axisWidget(yLeft);
1594 
1595  if( _magnifier && (obj == bottomAxis || obj == leftAxis)
1596  && !(isXYPlot() && _keep_aspect_ratio ) )
1597  {
1598  if( event->type() == QEvent::Wheel)
1599  {
1600  auto wheel_event = dynamic_cast<QWheelEvent*>(event);
1601  if( obj == bottomAxis ) {
1603  }
1604  else{
1606  }
1607  _magnifier->widgetWheelEvent(wheel_event);
1608  }
1609  }
1610 
1611  if( obj == canvas() )
1612  {
1613  if( _magnifier ){
1615  }
1616  return canvasEventFilter(event);
1617  }
1618 
1619  return false;
1620 }
1621 
1623 {
1624  switch( event->type() )
1625  {
1626  case QEvent::Wheel:
1627  {
1628  auto mouse_event = dynamic_cast<QWheelEvent*>(event);
1629 
1630  bool ctrl_modifier = mouse_event->modifiers() == Qt::ControlModifier;
1631  auto legend_rect = _legend->geometry( canvas()->rect() );
1632 
1633  if ( ctrl_modifier)
1634  {
1635  if( legend_rect.contains( mouse_event->pos() )
1636  && _legend->isVisible() )
1637  {
1638  int point_size = _legend->font().pointSize();
1639  if( mouse_event->delta() > 0 && point_size < 12)
1640  {
1641  emit legendSizeChanged(point_size+1);
1642  }
1643  if( mouse_event->delta() < 0 && point_size > 6)
1644  {
1645  emit legendSizeChanged(point_size-1);
1646  }
1647  return true; // don't pass to canvas().
1648  }
1649  }
1650 
1651  return false;
1652  }
1653 
1654  case QEvent::MouseButtonPress:
1655  {
1656  if( _dragging.mode != DragInfo::NONE)
1657  {
1658  return true; // don't pass to canvas().
1659  }
1660 
1661  QMouseEvent *mouse_event = static_cast<QMouseEvent*>(event);
1662 
1663  if( mouse_event->button() == Qt::LeftButton )
1664  {
1665  const QPoint press_point = mouse_event->pos();
1666  if( mouse_event->modifiers() == Qt::ShiftModifier) // time tracker
1667  {
1668  QPointF pointF ( invTransform( xBottom, press_point.x()),
1669  invTransform( yLeft, press_point.y()) );
1670  emit trackerMoved(pointF);
1671  return true; // don't pass to canvas().
1672  }
1673  else if( mouse_event->modifiers() == Qt::ControlModifier) // panner
1674  {
1675  QApplication::setOverrideCursor(QCursor(QPixmap(":/icons/resources/light/move.png")));
1676  }
1677  else{
1678  auto clicked_item = _legend->processMousePressEvent(mouse_event);
1679  if( clicked_item )
1680  {
1681  for( const auto& curve_it: _curve_list)
1682  {
1683  if( clicked_item == curve_it.second)
1684  {
1685  auto &curve = _curve_list.at( curve_it.first );
1686  curve->setVisible( !curve->isVisible() );
1687  _tracker->redraw();
1688  replot();
1689  return true;
1690  }
1691  }
1692  }
1693  }
1694  return false; // send to canvas()
1695  }
1696  else if ( mouse_event->buttons() == Qt::MidButton &&
1697  mouse_event->modifiers() == Qt::NoModifier )
1698  {
1699  QApplication::setOverrideCursor(QCursor(QPixmap(":/icons/resource/lights/move.png")));
1700  return false;
1701  }
1702  else if( mouse_event->button() == Qt::RightButton )
1703  {
1704  if( mouse_event->modifiers() == Qt::NoModifier) // show menu
1705  {
1706  canvasContextMenuTriggered( mouse_event->pos() );
1707  return true; // don't pass to canvas().
1708  }
1709  else if( mouse_event->modifiers() == Qt::ControlModifier) // Start swapping two plots
1710  {
1711 
1712  QDrag *drag = new QDrag(this);
1713  QMimeData *mimeData = new QMimeData;
1714 
1715  QByteArray data;
1716  QDataStream dataStream(&data, QIODevice::WriteOnly);
1717 
1718  dataStream << this->windowTitle();
1719 
1720  mimeData->setData("plot_area", data );
1721  drag->setMimeData(mimeData);
1722  drag->exec();
1723 
1724  return true; // don't pass to canvas().
1725  }
1726  }
1727  }break;
1728  //---------------------------------
1729  case QEvent::MouseMove:
1730  {
1731  if( _dragging.mode != DragInfo::NONE)
1732  {
1733  return true; // don't pass to canvas().
1734  }
1735 
1736  QMouseEvent *mouse_event = static_cast<QMouseEvent*>(event);
1737 
1738  if ( mouse_event->buttons() == Qt::LeftButton &&
1739  mouse_event->modifiers() == Qt::ShiftModifier )
1740  {
1741  const QPoint point = mouse_event->pos();
1742  QPointF pointF ( invTransform( xBottom, point.x()),
1743  invTransform( yLeft, point.y()) );
1744  emit trackerMoved(pointF);
1745  return true;
1746  }
1747  }break;
1748 
1749  case QEvent::Leave:
1750  {
1751  if( _dragging.mode == DragInfo::NONE )
1752  {
1753  changeBackgroundColor( Qt::white );
1754  QApplication::restoreOverrideCursor();
1755  return false;
1756  }
1757  }break;
1758  case QEvent::MouseButtonRelease :
1759  {
1760  if( _dragging.mode == DragInfo::NONE )
1761  {
1762  changeBackgroundColor( Qt::white );
1763  QApplication::restoreOverrideCursor();
1764  return false;
1765  }
1766  }break;
1767 
1768  case QEvent::Enter:
1769  {
1770  // If you think that this code doesn't make sense, you are right.
1771  // This is the workaround I have eventually found to avoid the problem with spurious
1772  // QEvent::DragLeave (I have never found the origin of the bug).
1773  dropEvent(nullptr);
1774  return true;
1775  }break;
1776 
1777  default: {}
1778 
1779  } //end switch
1780 
1781  return false;
1782 }
1783 
1785 {
1786  if( _mapped_data.numeric.size() > 0)
1787  {
1788  double min = std::numeric_limits<double>::max();
1789  double max = -std::numeric_limits<double>::max();
1790  for (auto& it: _mapped_data.numeric )
1791  {
1792  const PlotData& data = it.second;
1793  if( data.size() > 0){
1794  double A = data.front().x;
1795  double B = data.back().x;
1796  min = std::min( A, min );
1797  max = std::max( B, max );
1798  }
1799  }
1800  this->setAxisScale( xBottom, min - _time_offset, max - _time_offset);
1801  }
1802 }
1803 
1804 DataSeriesBase *PlotWidget::createSeriesData(const QString &ID, const PlotData *data)
1805 {
1806  DataSeriesBase *output = nullptr;
1807 
1808  if(ID.isEmpty() || ID == noTransform)
1809  {
1810  output = new Timeseries_NoTransform( data );
1811  }
1812  else if( ID == Derivative1st || ID == "firstDerivative")
1813  {
1814  output = new Timeseries_1stDerivative( data );
1815  }
1816  else if( ID == Derivative2nd || ID == "secondDerivative")
1817  {
1818  output = new Timeseries_2ndDerivative( data );
1819  }
1820  if( ID == "XYPlot")
1821  {
1822  try {
1823  output = new PointSeriesXY( data, _axisX );
1824  }
1825  catch (std::runtime_error& ex)
1826  {
1828  {
1829  QMessageBox msgBox(this);
1830  msgBox.setWindowTitle("Warnings");
1831  msgBox.setText( tr("The creation of the XY plot failed with the following message:\n %1")
1832  .arg( ex.what()) );
1833 
1834  // QAbstractButton* buttonDontRepear = msgBox.addButton("Don't show again",
1835  // QMessageBox::ActionRole);
1836  msgBox.addButton("Continue", QMessageBox::AcceptRole);
1837  msgBox.exec();
1838 
1839  // if (msgBox.clickedButton() == buttonDontRepear)
1840  // {
1841  // if_xy_plot_failed_show_dialog = false;
1842  // }
1843  }
1844  throw std::runtime_error("Creation of XY plot failed");
1845  }
1846  }
1847  auto custom_it = _snippets.find(ID);
1848  if( custom_it != _snippets.end())
1849  {
1850  const auto& snippet = custom_it->second;
1851  output = new CustomTimeseries( data, snippet, _mapped_data );
1852  }
1853 
1854  if( !output ){
1855  throw std::runtime_error("Not recognized ID in createSeriesData: ");
1856  }
1857  output->setTimeOffset( _time_offset );
1858  return output;
1859 }
1860 
1862 {
1863  if( canvasBackground().color() != color)
1864  {
1865  setCanvasBackground( color );
1866  replot();
1867  }
1868 }
1869 
1871 {
1872  auto font = _legend->font();
1873  font.setPointSize( size );
1874  _legend->setFont( font );
1875  replot();
1876 }
1877 
1879 {
1880  return _legend && _legend->isVisible();
1881 }
1882 
1883 void PlotWidget::setLegendAlignment(Qt::Alignment alignment)
1884 {
1885  _legend->setAlignment( Qt::Alignment( Qt::AlignTop | alignment ) );
1886 }
1887 
1888 void PlotWidget::setZoomEnabled(bool enabled)
1889 {
1890  _zoom_enabled = enabled;
1891  _zoomer->setEnabled( enabled );
1892  _magnifier->setEnabled( enabled );
1893  _panner1->setEnabled( enabled );
1894  _panner2->setEnabled( enabled );
1895 }
1896 
1898 {
1899  return _zoom_enabled;
1900 }
1901 
1902 
1903 
1905 {
1906  static int replot_count = 0;
1907 
1908  if( _zoomer ){
1909  _zoomer->setZoomBase( false );
1910  }
1911 
1912  QwtPlot::replot();
1913  // qDebug() << replot_count++;
1914 }
1915 
void setConstantRatioXY(bool active)
Definition: plotwidget.cpp:936
void setSpacing(int)
Set the spacing between the legend items.
static QStringList builtin_trans
Definition: plotwidget.cpp:58
void launchRemoveCurveDialog()
const QwtPlotItem * processMousePressEvent(QMouseEvent *mouse_event)
Definition: plotlegend.cpp:117
std::map< std::string, QwtPlotMarker * > _point_marker
Definition: plotwidget.h:157
CurveTracker * _tracker
Definition: plotwidget.h:179
PlotData::RangeValue _custom_Y_limits
Definition: plotwidget.h:215
std::unordered_map< std::string, PlotData > numeric
Definition: plotdata.h:144
bool anyColorModified() const
void setBackgroundMode(BackgroundMode)
Set the background mode.
static const char * Derivative1st
Definition: plotwidget.cpp:54
void on_savePlotToFile()
void on_panned(int dx, int dy)
Definition: plotwidget.cpp:614
Enable antialiasing.
void on_changeToBuiltinTransforms(QString new_transform)
A plot item, that represents a series of points.
X axis above the canvas.
Definition: qwt_plot.h:105
void setFont(const QFont &)
Definition: qwt_text.cpp:296
virtual QwtText label(double v) const
Convert a value into its representing label.
Definition: plotwidget.cpp:39
void updateMaximumZoomArea()
Definition: plotwidget.cpp:831
void replot() override
QAction * _action_changeColorsDialog
Definition: plotwidget.h:161
void setLegendSize(int size)
QDomElement xmlSaveState(QDomDocument &doc) const
Definition: plotwidget.cpp:619
void on_convertToXY_triggered(bool checked)
void setCanvasBackground(const QBrush &)
Change the background of the plotting area.
Definition: qwt_plot.cpp:861
QAction * _action_removeAllCurves
Definition: plotwidget.h:160
PlotLegend * _legend
Definition: plotwidget.h:180
bool isVisible() const
PlotData::RangeTime getMaximumRangeX() const
void setAxisEnabled(int axis, bool on)
En/Disable an axis.
const std::map< std::string, QwtPlotCurve * > & curveList() const
Definition: plotwidget.cpp:478
virtual nonstd::optional< QPointF > sampleFromTime(double t)=0
void rectChanged(PlotWidget *self, QRectF rect)
void changeColor(QString, QColor)
void enableMin(bool enabled, double value)
std::map< std::string, QwtPlotCurve * > _curve_list
Definition: plotwidget.h:156
DragInfo _dragging
Definition: plotwidget.h:195
void on_changeDateTimeScale(bool enable)
bool isXYPlot() const
QAction * _action_showPoints
Definition: plotwidget.h:162
PlotDataMapRef & _mapped_data
Definition: plotwidget.h:185
void on_externallyResized(const QRectF &new_rect)
virtual PlotData::RangeTimeOpt getVisualizationRangeX()
Definition: series_data.h:49
void undoableChange()
void legendSizeChanged(int new_size)
void setBackgroundBrush(const QBrush &)
Set the background brush.
void enableXMin(bool tf)
Enable or disable minor vertical grid lines.
void setAxisScaleDraw(int axisId, QwtScaleDraw *)
Set a scale draw.
void detachAllCurves()
Definition: plotwidget.cpp:593
QRectF canvasBoundingRect() const
Definition: plotwidget.cpp:821
SnippetsMap _snippets
Definition: plotwidget.h:221
bool _use_date_time_scale
Definition: plotwidget.h:183
void setCanvas(QWidget *)
Set the drawing canvas of the plot widget.
Definition: qwt_plot.cpp:217
void setFont(const QFont &)
QwtPlotPanner * _panner2
Definition: plotwidget.h:177
void swapWidgetsRequested(PlotWidget *source, PlotWidget *destination)
Y axis right of the canvas.
Definition: qwt_plot.h:99
PlotMagnifier * _magnifier
Definition: plotwidget.h:175
PlotWidget(PlotDataMapRef &datamap, QWidget *parent=nullptr)
Definition: plotwidget.cpp:64
virtual QRect geometry(const QRectF &canvasRect) const
void on_customTransformsDialog()
virtual void replot()
Redraw the plot.
Definition: qwt_plot.cpp:546
double _time_offset
Definition: plotwidget.h:211
bool isLegendVisible() const
QColor randomColorHint()
Definition: random_color.h:6
void activateLegend(bool activate)
double invTransform(int axisId, int pos) const
void enableX(bool tf)
Enable or disable vertical grid lines.
PlotZoomer * _zoomer
Definition: plotwidget.h:174
bool isEmpty() const
Definition: plotwidget.cpp:473
Canvas of a QwtPlot.
A 2-D plotting widget.
Definition: qwt_plot.h:74
AxisLimitsDialog * _axis_limits_dialog
Definition: plotwidget.h:217
void setTextPen(const QPen &)
Set the pen for drawing text labels.
A class for drawing symbols.
Definition: qwt_symbol.h:30
void setMargin(int)
Set the margin around legend items.
Y axis left of the canvas.
Definition: qwt_plot.h:96
const double MAX_DOUBLE
Definition: plotwidget.cpp:51
Renderer for exporting a plot to a document, a printer or anything else, that is supported by QPainte...
const std::string & name() const
Definition: plotdata.h:84
virtual bool event(QEvent *)
Adds handling of layout requests.
Definition: qwt_plot.cpp:241
DataSeriesBase * createSeriesData(const QString &ID, const PlotData *data)
const QwtScaleWidget * axisWidget(int axisId) const
void setTimeOffset(double offset)
Definition: series_data.h:35
void trackerMoved(QPointF pos)
enum PlotWidget::DragInfo::@3 mode
bool isZoomEnabled() const
void reloadPlotData()
Definition: plotwidget.cpp:980
QBrush canvasBackground() const
const PlotData * _axisX
Definition: plotwidget.h:213
void setPen(const QColor &, qreal width=0.0, Qt::PenStyle=Qt::SolidLine)
void setAlignment(Qt::Alignment)
Set the alignmnet.
QwtPlotPanner * _panner1
Definition: plotwidget.h:176
void updateCurves()
virtual void widgetWheelEvent(QWheelEvent *event) override
void zoomOut(bool emit_signal)
QAction * _action_custom_transform
Definition: plotwidget.h:170
void on_showPoints_triggered()
void curvesDropped()
void setEnabled(bool)
En/disable the magnifier.
static QStringList getChannelsFromFuntion(const QString &function)
void enableMax(bool enabled, double value)
QAction * _action_zoomOutVertically
Definition: plotwidget.h:165
virtual void updateLayout()
Adjust plot content to its current size.
Definition: qwt_plot.cpp:578
QAction * _action_zoomOutMaximum
Definition: plotwidget.h:163
void setAxisAutoScale(int axisId, bool on=true)
Enable autoscaling for a specified axis.
A Widget which contains a scale.
QAction * _action_2ndDerivativeTransform
Definition: plotwidget.h:168
QRectF _max_zoom_rect
Definition: plotwidget.h:227
void dropEvent(QDropEvent *event) override
Definition: plotwidget.cpp:543
void setDefaultMode(AxisMode mode)
Definition: plotmagnifier.h:27
QAction * _action_removeCurve
Definition: plotwidget.h:159
bool limitsEnabled() const
void setZoomInKey(int key, Qt::KeyboardModifiers=Qt::NoModifier)
const Point & front() const
Definition: plotdata.h:112
A class which draws a coordinate grid.
Definition: qwt_plot_grid.h:34
A class representing a text.
Definition: qwt_text.h:51
virtual QwtScaleMap canvasMap(int axisId) const
Definition: qwt_plot.cpp:790
void setAlignCanvasToScales(bool)
Set the align-canvas-to-axis-scales flag for all axes.
SnippetsMap GetSnippetsFromXML(const QString &xml_text)
bool canvasEventFilter(QEvent *event)
void rescaled(QRectF new_size)
static bool if_xy_plot_failed_show_dialog
Definition: plotwidget.cpp:56
virtual void updateLayout() override
Adjust plot content to its current size.
Definition: plotwidget.cpp:930
void changeAxisX(QString curve_name)
void configureTracker(CurveTracker::Parameter val)
QString _default_transform
Definition: plotwidget.h:186
void setFooter(const QString &)
Definition: qwt_plot.cpp:376
void on_zoomOutVertical_triggered(bool emit_signal=true)
std::string format(const std::string &, const time_point< seconds > &, const femtoseconds &, const time_zone &)
void panned(int dx, int dy)
void enableYMin(bool tf)
Enable or disable minor horizontal grid lines.
QAction * _action_phaseXY
Definition: plotwidget.h:169
virtual void resizeEvent(QResizeEvent *ev) override
Definition: plotwidget.cpp:919
void keepAspectratio(bool doKeep)
Definition: plotzoomer.h:16
QwtPlotGrid * _grid
Definition: plotwidget.h:181
void setPaintAttribute(PaintAttribute, bool on=true)
Changing the paint attributes.
void rescaleEqualAxisScaling()
Definition: plotwidget.cpp:874
virtual size_t size() const
Definition: plotdata.h:308
A scale map.
Definition: qwt_scale_map.h:30
bool addCurve(const std::string &name)
Definition: plotwidget.cpp:366
double invTransform(double p) const
char name[1]
QAction * _action_editLimits
Definition: plotwidget.h:172
void setEnabled(bool enable)
void setZoomOutKey(int key, Qt::KeyboardModifiers=Qt::NoModifier)
void setEnabled(bool)
En/disable the picker.
Definition: qwt_picker.cpp:387
void curveListChanged()
QwtPlotCurve::CurveStyle _curve_style
Definition: plotwidget.h:205
action
void on_changeColor(QString curve_name, QColor new_color)
bool xmlLoadState(QDomElement &element)
Definition: plotwidget.cpp:674
void setBorderPen(const QPen &)
QColor getColorHint() const
Definition: plotdata.h:314
void changeBackgroundColor(QColor color)
void on_zoomOutHorizontal_triggered(bool emit_signal=true)
bool eventFilter(QObject *obj, QEvent *event) override
Event filter.
QAction * _action_1stDerivativeTransform
Definition: plotwidget.h:167
double max_ratio
virtual bool updateCache()=0
PlotData::RangeValue getMaximumRangeY(PlotData::RangeTime range_X) const
TransformSelector * _transform_select_dialog
Definition: plotwidget.h:219
void addCurveName(const QString &name, const QColor &color)
virtual void setVisible(bool)
void buildActions()
Definition: plotwidget.cpp:164
virtual ~PlotWidget() override
Definition: plotwidget.cpp:361
static const char * noTransform
Definition: plotwidget.cpp:53
bool _zoom_enabled
Definition: plotwidget.h:223
virtual void setZoomBase(bool doReplot=true)
QWidget * canvas()
Definition: qwt_plot.cpp:467
void setTrackerPosition(double abs_time)
void setZoomEnabled(bool enabled)
void attach(QwtPlot *plot)
Attach the item to a plot.
QAction * _action_saveToFile
Definition: plotwidget.h:171
void setAxisLimits(int axis, double lower, double upper)
QwtPlotPanner provides panning of a plot canvas.
bool _keep_aspect_ratio
Definition: plotwidget.h:225
void on_changeTimeOffset(double offset)
void * arg
int min(int a, int b)
double transform(int axisId, double value) const
Transform a value into a coordinate in the plotting region.
void setLegendAlignment(Qt::Alignment alignment)
void on_changeColorsDialog_triggered()
std::map< std::string, QString > _curves_transform
Definition: plotwidget.h:187
QwtScaleEngine * axisScaleEngine(int axisId)
void setRenderHint(RenderHint, bool on=true)
std::vector< QString > curves
Definition: plotwidget.h:191
void setMousePattern(MousePatternCode, Qt::MouseButton button, Qt::KeyboardModifiers=Qt::NoModifier)
A class for drawing scales.
void setDefaultRangeX()
void setPosition(const QPointF &pos)
virtual void render(QwtPlot *, QPainter *, const QRectF &rect) const
const Point & back() const
Definition: plotdata.h:114
void transformCustomCurves()
void setColorHint(QColor color)
Definition: plotdata.h:320
void on_editAxisLimits_triggered()
empty_struct data[sizeof(T)/sizeof(empty_struct)]
void setZoomRectangle(QRectF rect, bool emit_signal)
Definition: plotwidget.cpp:949
void setMouseButton(Qt::MouseButton, Qt::KeyboardModifiers=Qt::NoModifier)
Definition: qwt_panner.cpp:109
void updateAvailableTransformers()
virtual void resizeEvent(QResizeEvent *e)
Definition: qwt_plot.cpp:531
void activateGrid(bool activate)
static const char * Derivative2nd
Definition: plotwidget.cpp:55
void setParameter(Parameter par)
void removeCurve(const std::string &name)
Definition: plotwidget.cpp:426
void enableY(bool tf)
Enable or disable horizontal grid lines.
void setRubberBandPen(const QPen &)
Definition: qwt_picker.cpp:471
void buildLegend()
Definition: plotwidget.cpp:330
QAction * _action_zoomOutHorizontally
Definition: plotwidget.h:164
void setAxisScale(int axisId, double min, double max, double step=0)
Disable autoscaling and specify a fixed scale for a selected axis.
void setMouseButton(Qt::MouseButton, Qt::KeyboardModifiers=Qt::NoModifier)
void setAttribute(Attribute, bool on=true)
void zoomed(const QRectF &rect)
void enableTracker(bool enable)
void dragEnterEvent(QDragEnterEvent *event) override
Definition: plotwidget.cpp:483
QAction * _action_noTransform
Definition: plotwidget.h:166
void updateAxes()
Rebuild the axes scales.
void setEnabled(bool)
En/disable the panner.
Definition: qwt_panner.cpp:187
void setTrackerPen(const QPen &)
Definition: qwt_picker.cpp:447
void dragLeaveEvent(QDragLeaveEvent *event) override
Definition: plotwidget.cpp:528
X axis below the canvas.
Definition: qwt_plot.h:102
QwtPlotLayout * plotLayout()
Definition: qwt_plot.cpp:434
A class for drawing markers.
PlotData::RangeValue rangeY() const
void setMaxColumns(uint)
Limit the number of columns.
void canvasContextMenuTriggered(const QPoint &pos)
Definition: plotwidget.cpp:286
void setDefaultRange(PlotData::RangeValue range)
Paint double buffered reusing the content of the pixmap buffer when possible.


plotjuggler
Author(s): Davide Faconti
autogenerated on Sat Jul 6 2019 03:44:17