00001 #include <QAction>
00002 #include <QActionGroup>
00003 #include <QApplication>
00004 #include <QDebug>
00005 #include <QDrag>
00006 #include <QDragEnterEvent>
00007 #include <QDragMoveEvent>
00008 #include <QFileDialog>
00009 #include <QMessageBox>
00010 #include <QMenu>
00011 #include <QMimeData>
00012 #include <QPushButton>
00013 #include <QWheelEvent>
00014 #include <QSettings>
00015 #include <iostream>
00016 #include <limits>
00017 #include <set>
00018 #include <memory>
00019 #include <QtXml/QDomElement>
00020 #include "qwt_scale_widget.h"
00021 #include "qwt_plot_canvas.h"
00022 #include "qwt_scale_engine.h"
00023 #include "qwt_plot_layout.h"
00024 #include "qwt_scale_draw.h"
00025 #include "qwt_text.h"
00026 #include "plotwidget.h"
00027 #include "removecurvedialog.h"
00028 #include "curvecolorpick.h"
00029 #include "qwt_plot_renderer.h"
00030 #include "qwt_series_data.h"
00031 #include "qwt_date_scale_draw.h"
00032 #include "PlotJuggler/random_color.h"
00033 #include "point_series_xy.h"
00034 #include "transforms/custom_function.h"
00035 #include "transforms/custom_timeseries.h"
00036
00037 class TimeScaleDraw: public QwtScaleDraw
00038 {
00039 virtual QwtText label(double v) const
00040 {
00041 QDateTime dt = QDateTime::fromMSecsSinceEpoch((qint64)(v*1000));
00042 if( dt.date().year() == 1970 && dt.date().month() == 1 && dt.date().day() == 1)
00043 {
00044 return dt.toString("hh:mm:ss.z");
00045 }
00046 return dt.toString("hh:mm:ss.z\nyyyy MMM dd");
00047
00048 }
00049 };
00050
00051 const double MAX_DOUBLE = std::numeric_limits<double>::max() / 2 ;
00052
00053 static const char* noTransform = "noTransform";
00054 static const char* Derivative1st = "1st Derivative";
00055 static const char* Derivative2nd = "2nd Derivative";
00056 static bool if_xy_plot_failed_show_dialog = true;
00057
00058 static QStringList builtin_trans = {
00059 noTransform,
00060 Derivative1st,
00061 Derivative2nd
00062 };
00063
00064 PlotWidget::PlotWidget(PlotDataMapRef &datamap, QWidget *parent):
00065 QwtPlot(parent),
00066 _zoomer( nullptr ),
00067 _magnifier( nullptr ),
00068 _panner1( nullptr ),
00069 _panner2( nullptr ),
00070 _tracker ( nullptr ),
00071 _legend( nullptr ),
00072 _grid( nullptr ),
00073 _mapped_data( datamap ),
00074 _dragging( { DragInfo::NONE, {}, nullptr } ),
00075 _curve_style(QwtPlotCurve::Lines),
00076 _time_offset(0.0),
00077 _axisX(nullptr),
00078 _transform_select_dialog(nullptr),
00079 _use_date_time_scale(false),
00080 _zoom_enabled(true),
00081 _keep_aspect_ratio(true)
00082 {
00083 _default_transform = "noTransform";
00084 connect(this, &PlotWidget::curveListChanged, this, [this](){ this->updateMaximumZoomArea(); } );
00085
00086 this->setAcceptDrops( true );
00087
00088 this->setMinimumWidth( 100 );
00089 this->setMinimumHeight( 100 );
00090
00091 this->sizePolicy().setHorizontalPolicy( QSizePolicy::Expanding);
00092 this->sizePolicy().setVerticalPolicy( QSizePolicy::Expanding);
00093
00094 QwtPlotCanvas *canvas = new QwtPlotCanvas(this);
00095
00096 canvas->setFrameStyle( QFrame::NoFrame );
00097 canvas->setPaintAttribute( QwtPlotCanvas::BackingStore, true );
00098
00099 this->setCanvas( canvas );
00100 this->setCanvasBackground( Qt::white );
00101
00102 this->setAxisAutoScale(QwtPlot::yLeft, true);
00103 this->setAxisAutoScale(QwtPlot::xBottom, true);
00104
00105 this->axisScaleEngine(QwtPlot::xBottom)->setAttribute(QwtScaleEngine::Floating,true);
00106 this->plotLayout()->setAlignCanvasToScales( true );
00107
00108
00109 _grid = new QwtPlotGrid();
00110 _zoomer = ( new PlotZoomer( this->canvas() ) );
00111 _magnifier = ( new PlotMagnifier( this->canvas() ) );
00112 _panner1 = ( new QwtPlotPanner( this->canvas() ) );
00113 _panner2 = ( new QwtPlotPanner( this->canvas() ) );
00114 _tracker = ( new CurveTracker( this ) );
00115
00116 _grid->setPen(QPen(Qt::gray, 0.0, Qt::DotLine));
00117
00118 _zoomer->setRubberBandPen( QColor( Qt::red , 1, Qt::DotLine) );
00119 _zoomer->setTrackerPen( QColor( Qt::green, 1, Qt::DotLine ) );
00120 _zoomer->setMousePattern( QwtEventPattern::MouseSelect1, Qt::LeftButton, Qt::NoModifier );
00121 connect(_zoomer, &PlotZoomer::zoomed, this, &PlotWidget::on_externallyResized );
00122
00123 _magnifier->setAxisEnabled(xTop, false);
00124 _magnifier->setAxisEnabled(yRight, false);
00125
00126 _magnifier->setZoomInKey( Qt::Key_Plus, Qt::ControlModifier);
00127 _magnifier->setZoomOutKey( Qt::Key_Minus, Qt::ControlModifier);
00128
00129
00130 _magnifier->setMouseButton( Qt::NoButton );
00131 connect(_magnifier, &PlotMagnifier::rescaled, this, [this](QRectF rect)
00132 {
00133 on_externallyResized(rect);
00134 replot();
00135 });
00136
00137 _panner1->setMouseButton( Qt::LeftButton, Qt::ControlModifier);
00138 _panner2->setMouseButton( Qt::MiddleButton, Qt::NoModifier);
00139
00140 connect(_panner1, &QwtPlotPanner::panned, this, &PlotWidget::on_panned );
00141 connect(_panner2, &QwtPlotPanner::panned, this, &PlotWidget::on_panned );
00142
00143
00144
00145 buildActions();
00146 buildLegend();
00147
00148 this->canvas()->setMouseTracking(true);
00149
00150 setDefaultRangeX();
00151
00152 _axis_limits_dialog = new AxisLimitsDialog(this);
00153
00154 _custom_Y_limits.min = (-MAX_DOUBLE );
00155 _custom_Y_limits.max = ( MAX_DOUBLE );
00156
00157 QwtScaleWidget *bottomAxis = this->axisWidget(xBottom);
00158 QwtScaleWidget *leftAxis = this->axisWidget(yLeft);
00159
00160 bottomAxis->installEventFilter(this);
00161 leftAxis->installEventFilter(this);
00162 }
00163
00164 void PlotWidget::buildActions()
00165 {
00166
00167 QIcon iconDeleteList;
00168
00169 auto getActionAndIcon = [this](const char* text, const char* file)
00170 {
00171 QIcon icon;
00172 icon.addFile( tr(file), QSize(30,30));
00173 auto action = new QAction( tr(text), this);
00174 action->setIcon(icon);
00175 return action;
00176 };
00177
00178 _action_removeCurve = getActionAndIcon("&Remove curves",
00179 ":/icons/resources/light/remove_list.png" );
00180 _action_removeCurve->setStatusTip(tr("Remove one or more curves from this plot"));
00181 connect(_action_removeCurve, &QAction::triggered, this, &PlotWidget::launchRemoveCurveDialog);
00182
00183 _action_removeAllCurves = getActionAndIcon("&Remove ALL curves",
00184 ":/icons/resources/light/remove.png" );
00185 connect(_action_removeAllCurves, &QAction::triggered, this, &PlotWidget::detachAllCurves);
00186 connect(_action_removeAllCurves, &QAction::triggered, this, &PlotWidget::undoableChange );
00187
00188 _action_changeColorsDialog = getActionAndIcon("&Change colors",
00189 ":/icons/resources/light/colored_charts.png" );
00190 _action_changeColorsDialog->setStatusTip(tr("Change the color of the curves"));
00191 connect(_action_changeColorsDialog, &QAction::triggered, this, &PlotWidget::on_changeColorsDialog_triggered);
00192
00193 _action_showPoints = getActionAndIcon("&Show lines and/or points",
00194 ":/icons/resources/light/point_chart.png" );
00195 connect(_action_showPoints, &QAction::triggered, this, &PlotWidget::on_showPoints_triggered);
00196
00197 _action_editLimits = new QAction(tr("&Edit Axis Limits"), this);
00198 connect(_action_editLimits, &QAction::triggered, this, &PlotWidget::on_editAxisLimits_triggered);
00199
00200 _action_zoomOutMaximum = getActionAndIcon("&Zoom Out", ":/icons/resources/light/zoom_max.png" );
00201 connect(_action_zoomOutMaximum, &QAction::triggered, this, [this]()
00202 {
00203 zoomOut(true);
00204 replot();
00205 emit undoableChange();
00206 });
00207
00208 _action_zoomOutHorizontally = getActionAndIcon("&Zoom Out Horizontally",
00209 ":/icons/resources/light/zoom_horizontal.png" );
00210 connect(_action_zoomOutHorizontally, &QAction::triggered, this, [this]()
00211 {
00212 on_zoomOutHorizontal_triggered(true);
00213 replot();
00214 emit undoableChange();
00215 });
00216
00217 _action_zoomOutVertically = getActionAndIcon("&Zoom Out Vertically",
00218 ":/icons/resources/light/zoom_vertical.png" );
00219 connect(_action_zoomOutVertically, &QAction::triggered, this, [this]()
00220 {
00221 on_zoomOutVertical_triggered(true);
00222 replot();
00223 emit undoableChange();
00224 });
00225
00226 QFont font;
00227 font.setPointSize(10);
00228
00229 _action_noTransform = new QAction(tr("&NO Transform"), this);
00230 _action_noTransform->setCheckable( true );
00231 _action_noTransform->setChecked( true );
00232 connect(_action_noTransform, &QAction::triggered, this, [this, font]()
00233 {
00234 QwtText text("");
00235 text.setFont(font);
00236 this->setFooter(text);
00237 this->on_changeToBuiltinTransforms(noTransform);
00238 } );
00239
00240 _action_1stDerivativeTransform = new QAction(tr("&1st Derivative"), this);
00241 _action_1stDerivativeTransform->setCheckable( true );
00242 connect(_action_1stDerivativeTransform, &QAction::triggered, this, [this, font]()
00243 {
00244 QwtText text("1st Derivative");
00245 text.setFont(font);
00246 this->setFooter(text);
00247 this->on_changeToBuiltinTransforms(Derivative1st);
00248 } );
00249
00250 _action_2ndDerivativeTransform = new QAction(tr("&2nd Derivative"), this);
00251 _action_2ndDerivativeTransform->setCheckable( true );
00252 connect(_action_2ndDerivativeTransform, &QAction::triggered, this, [this, font]()
00253 {
00254 QwtText text("2nd Derivative");
00255 text.setFont(font);
00256 this->setFooter(text);
00257 this->on_changeToBuiltinTransforms(Derivative2nd);
00258 } );
00259
00260 _action_phaseXY = new QAction(tr("&XY plot"), this);
00261 _action_phaseXY->setCheckable( true );
00262
00263 _action_phaseXY->setEnabled(false);
00264
00265 connect(_action_phaseXY, &QAction::triggered, this, &PlotWidget::on_convertToXY_triggered);
00266
00267 _action_custom_transform = new QAction(tr("&Custom..."), this);
00268 _action_custom_transform->setCheckable( true );
00269 connect(_action_custom_transform, &QAction::triggered,
00270 this, &PlotWidget::on_customTransformsDialog);
00271
00272 _action_saveToFile = getActionAndIcon("&Save plot to file",
00273 ":/icons/resources/light/save.png" );
00274 connect(_action_saveToFile, &QAction::triggered, this, &PlotWidget::on_savePlotToFile);
00275
00276 auto transform_group = new QActionGroup(this);
00277
00278 transform_group->addAction(_action_noTransform);
00279 transform_group->addAction(_action_1stDerivativeTransform);
00280 transform_group->addAction(_action_2ndDerivativeTransform);
00281 transform_group->addAction(_action_phaseXY);
00282 transform_group->addAction(_action_custom_transform);
00283 }
00284
00285
00286 void PlotWidget::canvasContextMenuTriggered(const QPoint &pos)
00287 {
00288 QString edit("&Edit Axis Limits ");
00289 edit.append( _axis_limits_dialog->limitsEnabled() ? tr("(ENABLED)") : tr("(disabled)") ) ;
00290 _action_editLimits->setText( edit );
00291
00292 QMenu menu(this);
00293 menu.addAction(_action_removeCurve);
00294 menu.addAction(_action_removeAllCurves);
00295 menu.addSeparator();
00296 menu.addAction(_action_changeColorsDialog);
00297 menu.addAction(_action_showPoints);
00298 menu.addSeparator();
00299 menu.addAction(_action_editLimits);
00300 menu.addAction(_action_zoomOutMaximum);
00301 menu.addAction(_action_zoomOutHorizontally);
00302 menu.addAction(_action_zoomOutVertically);
00303 menu.addSeparator();
00304 menu.addAction( _action_noTransform );
00305 menu.addAction( _action_1stDerivativeTransform );
00306 menu.addAction( _action_2ndDerivativeTransform );
00307 menu.addAction( _action_phaseXY );
00308 menu.addAction( _action_custom_transform );
00309 menu.addSeparator();
00310 menu.addAction( _action_saveToFile );
00311
00312 _action_removeCurve->setEnabled( ! _curve_list.empty() );
00313 _action_removeAllCurves->setEnabled( ! _curve_list.empty() );
00314 _action_changeColorsDialog->setEnabled( ! _curve_list.empty() );
00315 _action_phaseXY->setEnabled( _axisX != nullptr );
00316
00317 if( !_axisX )
00318 {
00319 menu.setToolTipsVisible(true);
00320 _action_phaseXY->setToolTip(
00321 "To show a XY plot, you must first provide the X axis.\n"
00322 "Drag andn drop a curve using the RIGHT mouse\n"
00323 "button instead of the left one." );
00324 }
00325
00326 menu.exec( canvas()->mapToGlobal(pos) );
00327 }
00328
00329
00330 void PlotWidget::buildLegend()
00331 {
00332 _legend = new PlotLegend(this);
00333
00334 _legend->attach( this );
00335
00336 _legend->setRenderHint( QwtPlotItem::RenderAntialiased );
00337 QColor color( Qt::black );
00338 _legend->setTextPen( color );
00339 _legend->setBorderPen( color );
00340 QColor c( Qt::white );
00341 c.setAlpha( 200 );
00342 _legend->setBackgroundBrush( c );
00343
00344 _legend->setMaxColumns( 1 );
00345 _legend->setAlignment( Qt::Alignment( Qt::AlignTop | Qt::AlignRight ) );
00346 _legend->setBackgroundMode( QwtPlotLegendItem::BackgroundMode::LegendBackground );
00347
00348 _legend->setBorderRadius( 4 );
00349 _legend->setMargin( 1 );
00350 _legend->setSpacing( 1 );
00351 _legend->setItemMargin( 1 );
00352
00353 QFont font = _legend->font();
00354 font.setPointSize( 9 );
00355 _legend->setFont( font );
00356 _legend->setVisible( true );
00357 }
00358
00359
00360
00361 PlotWidget::~PlotWidget()
00362 {
00363
00364 }
00365
00366 bool PlotWidget::addCurve(const std::string &name)
00367 {
00368 auto it = _mapped_data.numeric.find( name );
00369 if( it == _mapped_data.numeric.end())
00370 {
00371 return false;
00372 }
00373
00374 if( _curve_list.find(name) != _curve_list.end())
00375 {
00376 return false;
00377 }
00378
00379 PlotData& data = it->second;
00380 const auto qname = QString::fromStdString( name );
00381
00382 auto curve = new QwtPlotCurve( qname );
00383 try {
00384 auto plot_qwt = createSeriesData( _default_transform, &data );
00385 _curves_transform.insert( {name, _default_transform} );
00386
00387 curve->setPaintAttribute( QwtPlotCurve::ClipPolygons, true );
00388 curve->setPaintAttribute( QwtPlotCurve::FilterPointsAggressive, true );
00389 curve->setData( plot_qwt );
00390 }
00391 catch( std::exception& ex)
00392 {
00393 QMessageBox::warning(this, "Exception!", ex.what());
00394 return false;
00395 }
00396
00397 curve->setStyle( _curve_style );
00398
00399 QColor color = data.getColorHint();
00400 if( color == Qt::black)
00401 {
00402 color = randomColorHint();
00403 data.setColorHint(color);
00404 }
00405 curve->setPen( color, (_curve_style == QwtPlotCurve::Dots) ? 4 : 0.8 );
00406 curve->setRenderHint( QwtPlotItem::RenderAntialiased, true );
00407
00408 curve->attach( this );
00409 _curve_list.insert( std::make_pair(name, curve));
00410
00411 auto marker = new QwtPlotMarker;
00412 _point_marker.insert( std::make_pair(name, marker) );
00413 marker->attach( this );
00414 marker->setVisible( isXYPlot() );
00415
00416 QwtSymbol *sym = new QwtSymbol(
00417 QwtSymbol::Diamond,
00418 Qt::red, color,
00419 QSize(10,10));
00420
00421 marker->setSymbol(sym);
00422
00423 return true;
00424 }
00425
00426 void PlotWidget::removeCurve(const std::string &curve_name)
00427 {
00428 auto it = _curve_list.find(curve_name);
00429 if( it != _curve_list.end() )
00430 {
00431 auto& curve = it->second;
00432 curve->detach();
00433 _curve_list.erase( it );
00434
00435 auto marker_it = _point_marker.find(curve_name);
00436 if( marker_it != _point_marker.end() )
00437 {
00438 auto marker = marker_it->second;
00439 if( marker ){
00440 marker->detach();
00441 }
00442 _point_marker.erase(marker_it);
00443 }
00444 _tracker->redraw();
00445 emit curveListChanged();
00446 }
00447 _curves_transform.erase( curve_name );
00448
00449 if( isXYPlot() && _axisX && _axisX->name() == curve_name)
00450 {
00451
00452 _axisX = nullptr;
00453 _default_transform.clear();
00454 for(auto& it : _curve_list)
00455 {
00456 auto& curve = it.second;
00457
00458 auto data_it = _mapped_data.numeric.find( curve_name );
00459 if( data_it != _mapped_data.numeric.end())
00460 {
00461 const auto& data = data_it->second;
00462 auto data_series = createSeriesData( _default_transform, &data);
00463 curve->setData( data_series );
00464 }
00465 }
00466 on_changeToBuiltinTransforms( _default_transform );
00467
00468 _tracker->redraw();
00469 emit curveListChanged();
00470 }
00471 }
00472
00473 bool PlotWidget::isEmpty() const
00474 {
00475 return _curve_list.empty();
00476 }
00477
00478 const std::map<std::string, QwtPlotCurve*> &PlotWidget::curveList() const
00479 {
00480 return _curve_list;
00481 }
00482
00483 void PlotWidget::dragEnterEvent(QDragEnterEvent *event)
00484 {
00485 changeBackgroundColor( QColor( 230, 230, 230 ) );
00486
00487 const QMimeData *mimeData = event->mimeData();
00488 QStringList mimeFormats = mimeData->formats();
00489 _dragging.curves.clear();
00490 _dragging.source = event->source();
00491 for(const QString& format: mimeFormats)
00492 {
00493 QByteArray encoded = mimeData->data( format );
00494 QDataStream stream(&encoded, QIODevice::ReadOnly);
00495
00496 while (!stream.atEnd())
00497 {
00498 QString curve_name;
00499 stream >> curve_name;
00500 if(!curve_name.isEmpty()) {
00501 _dragging.curves.push_back(curve_name);
00502 }
00503 }
00504
00505 if( format.contains( "curveslist/add_curve") )
00506 {
00507 _dragging.mode = DragInfo::CURVES;
00508 event->acceptProposedAction();
00509 }
00510 if( format.contains( "curveslist/new_X_axis") && _dragging.curves.size() == 1 )
00511 {
00512 _dragging.mode = DragInfo::NEW_X;
00513 event->acceptProposedAction();
00514 }
00515 if( format.contains( "plot_area") )
00516 {
00517 if(_dragging.curves.size() == 1 &&
00518 windowTitle() != _dragging.curves.front() )
00519 {
00520 _dragging.mode = DragInfo::SWAP_PLOTS;
00521 event->acceptProposedAction();
00522 }
00523 }
00524 }
00525 }
00526
00527
00528 void PlotWidget::dragLeaveEvent(QDragLeaveEvent*)
00529 {
00530 QPoint local_pos = canvas()->mapFromGlobal(QCursor::pos()) ;
00531
00532 if( canvas()->rect().contains( local_pos ))
00533 {
00534
00535 }
00536 else{
00537 changeBackgroundColor( Qt::white );
00538 _dragging.mode = DragInfo::NONE;
00539 _dragging.curves.clear();
00540 }
00541 }
00542
00543 void PlotWidget::dropEvent(QDropEvent *)
00544 {
00545 bool curves_changed = false;
00546 bool background_changed = false;
00547
00548 if( _dragging.mode == DragInfo::CURVES)
00549 {
00550 for( const auto& curve_name : _dragging.curves)
00551 {
00552 bool added = addCurve( curve_name.toStdString() );
00553 curves_changed = curves_changed || added;
00554 }
00555 emit curvesDropped();
00556 }
00557 else if( _dragging.mode == DragInfo::NEW_X)
00558 {
00559 changeAxisX( _dragging.curves.front() );
00560 curves_changed = true;
00561 emit curvesDropped();
00562 }
00563 else if( _dragging.mode == DragInfo::SWAP_PLOTS )
00564 {
00565 auto plot_widget = dynamic_cast<PlotWidget*>(_dragging.source);
00566 if( plot_widget )
00567 {
00568 emit swapWidgetsRequested( plot_widget, this );
00569 }
00570 }
00571 if( _dragging.mode != DragInfo::NONE &&
00572 canvasBackground().color() != Qt::white)
00573 {
00574 this->setCanvasBackground( Qt::white );
00575 background_changed = true;
00576 }
00577
00578 if( curves_changed )
00579 {
00580 emit curveListChanged();
00581 zoomOut(false);
00582 emit undoableChange();
00583 }
00584 if( curves_changed || background_changed )
00585 {
00586 _tracker->redraw();
00587 replot();
00588 }
00589 _dragging.mode = DragInfo::NONE;
00590 _dragging.curves.clear();
00591 }
00592
00593 void PlotWidget::detachAllCurves()
00594 {
00595 for(auto& it: _curve_list) { it.second->detach(); }
00596 for(auto& it: _point_marker) { it.second->detach(); }
00597
00598 if( isXYPlot() )
00599 {
00600 _axisX = nullptr;
00601 _action_noTransform->trigger();
00602 }
00603 _curve_list.clear();
00604 _curves_transform.clear();
00605 _point_marker.clear();
00606
00607 _tracker->redraw();
00608
00609 emit curveListChanged();
00610
00611 replot();
00612 }
00613
00614 void PlotWidget::on_panned(int , int )
00615 {
00616 on_externallyResized(canvasBoundingRect());
00617 }
00618
00619 QDomElement PlotWidget::xmlSaveState( QDomDocument &doc) const
00620 {
00621 QDomElement plot_el = doc.createElement("plot");
00622
00623 QDomElement range_el = doc.createElement("range");
00624 QRectF rect = this->canvasBoundingRect();
00625 range_el.setAttribute("bottom", QString::number(rect.bottom(), 'f', 6) );
00626 range_el.setAttribute("top", QString::number(rect.top(), 'f', 6));
00627 range_el.setAttribute("left", QString::number(rect.left(), 'f', 6));
00628 range_el.setAttribute("right", QString::number(rect.right() ,'f', 6));
00629 plot_el.appendChild(range_el);
00630
00631 QDomElement limitY_el = doc.createElement("limitY");
00632 if( _custom_Y_limits.min > -MAX_DOUBLE){
00633 limitY_el.setAttribute("min", QString::number( _custom_Y_limits.min) );
00634 }
00635 if( _custom_Y_limits.max < MAX_DOUBLE){
00636 limitY_el.setAttribute("max", QString::number( _custom_Y_limits.max) );
00637 }
00638 plot_el.appendChild(limitY_el);
00639
00640 for(auto& it: _curve_list)
00641 {
00642 auto& name = it.first;
00643 auto& curve = it.second;
00644 QDomElement curve_el = doc.createElement("curve");
00645 curve_el.setAttribute( "name", QString::fromStdString( name ));
00646 curve_el.setAttribute( "R", curve->pen().color().red());
00647 curve_el.setAttribute( "G", curve->pen().color().green());
00648 curve_el.setAttribute( "B", curve->pen().color().blue());
00649 curve_el.setAttribute( "custom_transform", _curves_transform.at(name) );
00650
00651 plot_el.appendChild(curve_el);
00652 }
00653
00654 QDomElement transform = doc.createElement("transform");
00655
00656 if( _action_custom_transform->isChecked() )
00657 {
00658 transform.setAttribute("value", tr("Custom::") +_default_transform);
00659 }
00660 else{
00661 transform.setAttribute("value", _default_transform);
00662 }
00663
00664 if( _axisX )
00665 {
00666 transform.setAttribute("axisX", QString::fromStdString( _axisX->name()) );
00667 }
00668
00669 plot_el.appendChild(transform);
00670
00671 return plot_el;
00672 }
00673
00674 bool PlotWidget::xmlLoadState(QDomElement &plot_widget)
00675 {
00676 std::set<std::string> added_curve_names;
00677
00678 QDomElement transform = plot_widget.firstChildElement( "transform" );
00679
00680 QDomElement limitY_el = plot_widget.firstChildElement("limitY");
00681 if( !limitY_el.isNull() )
00682 {
00683 if( limitY_el.hasAttribute("min") ) {
00684 _custom_Y_limits.min = limitY_el.attribute("min").toDouble();
00685 _axis_limits_dialog->enableMin( true, _custom_Y_limits.min);
00686 }
00687 else{
00688 _custom_Y_limits.max = -MAX_DOUBLE;
00689 _axis_limits_dialog->enableMin( false, _custom_Y_limits.min);
00690 }
00691
00692 if( limitY_el.hasAttribute("max") ) {
00693 _custom_Y_limits.max = limitY_el.attribute("max").toDouble();
00694 _axis_limits_dialog->enableMax( true, _custom_Y_limits.max);
00695 }
00696 else{
00697 _custom_Y_limits.max = MAX_DOUBLE;
00698 _axis_limits_dialog->enableMax( false, _custom_Y_limits.max);
00699 }
00700 }
00701
00702 static bool warning_message_shown = false;
00703
00704 bool curve_added = false;
00705
00706 for (QDomElement curve_element = plot_widget.firstChildElement( "curve" ) ;
00707 !curve_element.isNull();
00708 curve_element = curve_element.nextSiblingElement( "curve" ) )
00709 {
00710 std::string curve_name = curve_element.attribute("name").toStdString();
00711 int R = curve_element.attribute("R").toInt();
00712 int G = curve_element.attribute("G").toInt();
00713 int B = curve_element.attribute("B").toInt();
00714 QColor color(R,G,B);
00715
00716 if( _mapped_data.numeric.find(curve_name) != _mapped_data.numeric.end() )
00717 {
00718 auto added = addCurve( curve_name );
00719 curve_added = curve_added || added;
00720 _curve_list[curve_name]->setPen( color, 1.0);
00721 added_curve_names.insert(curve_name );
00722 }
00723 else if( ! warning_message_shown )
00724 {
00725 QMessageBox::warning(this, "Warning",
00726 tr("Can't find one or more curves.\n"
00727 "This message will be shown only once.") );
00728 warning_message_shown = true;
00729 }
00730 }
00731
00732 bool curve_removed = true;
00733
00734 while( curve_removed )
00735 {
00736 curve_removed = false;
00737 for(auto& it: _curve_list)
00738 {
00739 auto curve_name = it.first;
00740 if( added_curve_names.find( curve_name ) == added_curve_names.end())
00741 {
00742 removeCurve( curve_name );
00743 curve_removed = true;
00744 break;
00745 }
00746 }
00747 }
00748
00749 if( !transform.isNull() )
00750 {
00751 QString trans_value = transform.attribute("value");
00752 if( trans_value.isEmpty() || trans_value == "noTransform" )
00753 {
00754 _action_noTransform->trigger();
00755 }
00756 else if( trans_value == Derivative1st )
00757 {
00758 _action_1stDerivativeTransform->trigger();
00759 }
00760 else if( trans_value == Derivative2nd )
00761 {
00762 _action_2ndDerivativeTransform->trigger();
00763 }
00764 else if( trans_value == "XYPlot" )
00765 {
00766 changeAxisX( transform.attribute("axisX") );
00767 _action_phaseXY->trigger();
00768 }
00769 else if( trans_value.startsWith("Custom::" ) )
00770 {
00771 _default_transform = trans_value.remove(0, 8);
00772
00773 updateAvailableTransformers();
00774
00775 for (QDomElement curve_element = plot_widget.firstChildElement( "curve" ) ;
00776 !curve_element.isNull();
00777 curve_element = curve_element.nextSiblingElement( "curve" ) )
00778 {
00779 std::string curve_name = curve_element.attribute("name").toStdString();
00780 auto custom_attribute = curve_element.attribute("custom_transform");
00781 if( !custom_attribute.isNull() )
00782 {
00783 _curves_transform[curve_name] = custom_attribute;
00784 }
00785 }
00786 transformCustomCurves();
00787 _action_custom_transform->setChecked(true);
00788 }
00789 }
00790
00791 if( curve_removed || curve_added)
00792 {
00793 _tracker->redraw();
00794 replot();
00795 emit curveListChanged();
00796 }
00797
00798
00799
00800 QDomElement rectangle = plot_widget.firstChildElement( "range" );
00801 if( isXYPlot() )
00802 {
00803 updateMaximumZoomArea();
00804 }
00805
00806 if( !rectangle.isNull() )
00807 {
00808 QRectF rect;
00809 rect.setBottom( rectangle.attribute("bottom").toDouble());
00810 rect.setTop( rectangle.attribute("top").toDouble());
00811 rect.setLeft( rectangle.attribute("left").toDouble());
00812 rect.setRight( rectangle.attribute("right").toDouble());
00813 this->setZoomRectangle( rect, false);
00814 }
00815
00816 replot();
00817 return true;
00818 }
00819
00820
00821 QRectF PlotWidget::canvasBoundingRect() const
00822 {
00823 QRectF rect;
00824 rect.setBottom( this->canvasMap( yLeft ).s1() );
00825 rect.setTop( this->canvasMap( yLeft ).s2() );
00826 rect.setLeft( this->canvasMap( xBottom ).s1() );
00827 rect.setRight( this->canvasMap( xBottom ).s2() );
00828 return rect;
00829 }
00830
00831 void PlotWidget::updateMaximumZoomArea()
00832 {
00833 QRectF max_rect ;
00834 auto rangeX = getMaximumRangeX();
00835 max_rect.setLeft( rangeX.min );
00836 max_rect.setRight( rangeX.max );
00837
00838 auto rangeY = getMaximumRangeY( rangeX );
00839 max_rect.setBottom( rangeY.min );
00840 max_rect.setTop( rangeY.max );
00841
00842 if( isXYPlot() && _keep_aspect_ratio )
00843 {
00844 const QRectF canvas_rect = canvas()->contentsRect();
00845 const double canvas_ratio = fabs( canvas_rect.width() / canvas_rect.height() );
00846 const double data_ratio = fabs(max_rect.width() / max_rect.height());
00847 if( data_ratio < canvas_ratio )
00848 {
00849
00850 double new_width = fabs( max_rect.height() * canvas_ratio );
00851 double increment = new_width - max_rect.width();
00852 max_rect.setWidth( new_width );
00853 max_rect.moveLeft( max_rect.left() - 0.5*increment );
00854 }
00855 else{
00856
00857 double new_height = -(max_rect.width() / canvas_ratio);
00858 double increment = fabs(new_height - max_rect.height());
00859 max_rect.setHeight( new_height );
00860 max_rect.moveTop( max_rect.top() + 0.5*increment );
00861 }
00862 _magnifier->setAxisLimits( xBottom, max_rect.left(), max_rect.right() );
00863 _magnifier->setAxisLimits( yLeft, max_rect.bottom(), max_rect.top() );
00864 _zoomer->keepAspectratio( true );
00865 }
00866 else{
00867 _magnifier->setAxisLimits( xBottom, max_rect.left(), max_rect.right() );
00868 _magnifier->setAxisLimits( yLeft, max_rect.bottom(), max_rect.top() );
00869 _zoomer->keepAspectratio( false );
00870 }
00871 _max_zoom_rect = max_rect;
00872 }
00873
00874 void PlotWidget::rescaleEqualAxisScaling()
00875 {
00876 const QwtScaleMap xMap = canvasMap( QwtPlot::xBottom );
00877 const QwtScaleMap yMap = canvasMap( QwtPlot::yLeft );
00878
00879 QRectF canvas_rect = canvas()->contentsRect();
00880 canvas_rect = canvas_rect.normalized();
00881 const double x1 = xMap.invTransform( canvas_rect.left() );
00882 const double x2 = xMap.invTransform( canvas_rect.right() );
00883 const double y1 = yMap.invTransform( canvas_rect.bottom() );
00884 const double y2 = yMap.invTransform( canvas_rect.top() );
00885
00886 const double data_ratio = ( x2 - x1 ) / ( y2 - y1 );
00887 const double canvas_ratio = canvas_rect.width() / canvas_rect.height();
00888 const double max_ratio = fabs( _max_zoom_rect.width() / _max_zoom_rect.height() );
00889
00890 QRectF rect( QPointF(x1,y2), QPointF(x2,y1) );
00891
00892 if( data_ratio < canvas_ratio )
00893 {
00894 double new_width = fabs( rect.height() * canvas_ratio );
00895 double increment = new_width - rect.width();
00896 rect.setWidth( new_width );
00897 rect.moveLeft( rect.left() - 0.5*increment );
00898 }
00899 else{
00900 double new_height = -(rect.width() / canvas_ratio);
00901 double increment = fabs(new_height - rect.height());
00902 rect.setHeight( new_height );
00903 rect.moveTop( rect.top() + 0.5*increment );
00904 }
00905 if( rect.contains(_max_zoom_rect) )
00906 {
00907 rect = _max_zoom_rect;
00908 }
00909
00910 this->setAxisScale( yLeft,
00911 std::min(rect.bottom(), rect.top() ),
00912 std::max(rect.bottom(), rect.top() ));
00913 this->setAxisScale( xBottom,
00914 std::min( rect.left(), rect.right()),
00915 std::max( rect.left(), rect.right()) );
00916 this->updateAxes();
00917 }
00918
00919 void PlotWidget::resizeEvent( QResizeEvent *ev )
00920 {
00921 QwtPlot::resizeEvent(ev);
00922 updateMaximumZoomArea();
00923
00924 if( isXYPlot() && _keep_aspect_ratio )
00925 {
00926 rescaleEqualAxisScaling();
00927 }
00928 }
00929
00930 void PlotWidget::updateLayout()
00931 {
00932 QwtPlot::updateLayout();
00933
00934 }
00935
00936 void PlotWidget::setConstantRatioXY(bool active)
00937 {
00938 _keep_aspect_ratio = active;
00939 if( isXYPlot() && active)
00940 {
00941
00942 _zoomer->keepAspectratio( true );
00943 }
00944 else{
00945 _zoomer->keepAspectratio( false );
00946 }
00947 }
00948
00949 void PlotWidget::setZoomRectangle(QRectF rect, bool emit_signal)
00950 {
00951 QRectF current_rect = canvasBoundingRect();
00952 if( current_rect == rect)
00953 {
00954 return;
00955 }
00956 this->setAxisScale( yLeft,
00957 std::min(rect.bottom(), rect.top() ),
00958 std::max(rect.bottom(), rect.top() ));
00959 this->setAxisScale( xBottom,
00960 std::min( rect.left(), rect.right()),
00961 std::max( rect.left(), rect.right()) );
00962 this->updateAxes();
00963
00964 if( isXYPlot() && _keep_aspect_ratio )
00965 {
00966 rescaleEqualAxisScaling();
00967 }
00968
00969 if( emit_signal )
00970 {
00971 if( isXYPlot()) {
00972 emit undoableChange();
00973 }
00974 else{
00975 emit rectChanged(this, rect);
00976 }
00977 }
00978 }
00979
00980 void PlotWidget::reloadPlotData()
00981 {
00982 if( isXYPlot() )
00983 {
00984 auto it = _mapped_data.numeric.find( _axisX->name() );
00985 if( it != _mapped_data.numeric.end() ){
00986 _axisX = &(it->second);
00987 }
00988 else{
00989 _axisX = nullptr;
00990 }
00991 }
00992
00993 for (auto& curve_it: _curve_list)
00994 {
00995 auto& curve = curve_it.second;
00996 const auto& curve_name = curve_it.first;
00997
00998 auto data_it = _mapped_data.numeric.find( curve_name );
00999 if( data_it != _mapped_data.numeric.end())
01000 {
01001 const auto& data = data_it->second;
01002 const auto& transform = _curves_transform.at(curve_name);
01003 auto data_series = createSeriesData( transform, &data);
01004 curve->setData( data_series );
01005 }
01006 }
01007
01008 if( _curve_list.size() == 0){
01009 setDefaultRangeX();
01010 }
01011 }
01012
01013 void PlotWidget::activateLegend(bool activate)
01014 {
01015 _legend->setVisible(activate);
01016 }
01017
01018 void PlotWidget::activateGrid(bool activate)
01019 {
01020 _grid->enableX(activate);
01021 _grid->enableXMin(activate);
01022 _grid->enableY(activate);
01023 _grid->enableYMin(activate);
01024 _grid->attach(this);
01025 }
01026
01027 void PlotWidget::configureTracker(CurveTracker::Parameter val)
01028 {
01029 _tracker->setParameter( val );
01030 }
01031
01032 void PlotWidget::enableTracker(bool enable)
01033 {
01034 _tracker->setEnabled( enable && !isXYPlot() );
01035 }
01036
01037 void PlotWidget::setTrackerPosition(double abs_time)
01038 {
01039 if( isXYPlot()){
01040 for (auto& it: _curve_list)
01041 {
01042 auto& name = it.first;
01043 auto series = static_cast<DataSeriesBase*>( it.second->data() );
01044 auto pointXY = series->sampleFromTime(abs_time);
01045 if( pointXY ){
01046 _point_marker[name]->setValue( pointXY.value() );
01047 }
01048 }
01049 }
01050 else{
01051 double relative_time = abs_time - _time_offset;
01052 _tracker->setPosition( QPointF( relative_time , 0.0) );
01053 }
01054 }
01055
01056 void PlotWidget::on_changeTimeOffset(double offset)
01057 {
01058 auto prev_offset = _time_offset;
01059 _time_offset = offset;
01060
01061 if( fabs( prev_offset - offset) > std::numeric_limits<double>::epsilon() )
01062 {
01063 for(auto& it: _curve_list)
01064 {
01065 auto series = static_cast<DataSeriesBase*>( it.second->data() );
01066 series->setTimeOffset(_time_offset);
01067 }
01068 if( !isXYPlot() )
01069 {
01070 QRectF rect = canvasBoundingRect();
01071 double delta = prev_offset - offset;
01072 rect.moveLeft( rect.left() + delta );
01073 setZoomRectangle( rect, false );
01074 }
01075 }
01076 }
01077
01078 void PlotWidget::on_changeDateTimeScale(bool enable)
01079 {
01080 if( enable != _use_date_time_scale)
01081 {
01082 _use_date_time_scale = enable;
01083 if( enable )
01084 {
01085 setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw());
01086 }
01087 else{
01088 setAxisScaleDraw(QwtPlot::xBottom, new QwtScaleDraw);
01089 }
01090 }
01091 }
01092
01093
01094 PlotData::RangeTime PlotWidget::getMaximumRangeX() const
01095 {
01096 double left = std::numeric_limits<double>::max();
01097 double right = -std::numeric_limits<double>::max();
01098
01099 for(auto& it: _curve_list)
01100 {
01101 auto series = static_cast<DataSeriesBase*>( it.second->data() );
01102 const auto max_range_X = series->getVisualizationRangeX();
01103 if( !max_range_X ) continue;
01104
01105 left = std::min(max_range_X->min, left);
01106 right = std::max(max_range_X->max, right);
01107 }
01108
01109 if( left > right ){
01110 left = 0;
01111 right = 0;
01112 }
01113
01114 double margin = 0.0;
01115 if( fabs(right - left) > std::numeric_limits<double>::epsilon() )
01116 {
01117 margin = isXYPlot() ? ((right-left) * 0.025) : 0.0;
01118 }
01119 right = right + margin;
01120 left = left - margin;
01121
01122 return PlotData::RangeTime( {left,right} );
01123 }
01124
01125
01126 PlotData::RangeValue PlotWidget::getMaximumRangeY( PlotData::RangeTime range_X) const
01127 {
01128 double top = -std::numeric_limits<double>::max();
01129 double bottom = std::numeric_limits<double>::max();
01130
01131 for(auto& it: _curve_list)
01132 {
01133 auto series = static_cast<DataSeriesBase*>( it.second->data() );
01134
01135 const auto max_range_X = series->getVisualizationRangeX();
01136 if( !max_range_X ) continue;
01137
01138 double left = std::max(max_range_X->min, range_X.min);
01139 double right = std::min(max_range_X->max, range_X.max);
01140
01141 left += _time_offset;
01142 right += _time_offset;
01143 left = std::nextafter(left, right);
01144 right = std::nextafter(right, left);
01145
01146 auto range_Y = series->getVisualizationRangeY( {left, right} );
01147 if( !range_Y )
01148 {
01149 qDebug() << " invalid range_Y in PlotWidget::maximumRangeY";
01150 continue;
01151 }
01152 if( top < range_Y->max ) top = range_Y->max;
01153 if( bottom > range_Y->min ) bottom = range_Y->min;
01154 }
01155
01156 double margin = 0.1;
01157
01158 if( bottom > top ){
01159 bottom = 0;
01160 top = 0;
01161 }
01162
01163 if( top - bottom > std::numeric_limits<double>::epsilon() )
01164 {
01165 margin = (top-bottom) * 0.025;
01166 }
01167
01168 const bool lower_limit = _custom_Y_limits.min > -MAX_DOUBLE;
01169 const bool upper_limit = _custom_Y_limits.max < MAX_DOUBLE;
01170
01171 if(lower_limit)
01172 {
01173 bottom = _custom_Y_limits.min;
01174 if( top < bottom ) top = bottom + margin;
01175 }
01176
01177 if( upper_limit )
01178 {
01179 top = _custom_Y_limits.max;
01180 if( top < bottom ) bottom = top - margin;
01181 }
01182
01183 if( !lower_limit && !upper_limit )
01184 {
01185 top += margin;
01186 bottom -= margin;
01187 }
01188
01189 return PlotData::RangeValue({ bottom, top});
01190 }
01191
01192
01193 void PlotWidget::updateCurves()
01194 {
01195 for(auto& it: _curve_list)
01196 {
01197 auto series = static_cast<DataSeriesBase*>( it.second->data() );
01198 bool res = series->updateCache();
01199
01200 }
01201 }
01202
01203 void PlotWidget::launchRemoveCurveDialog()
01204 {
01205 RemoveCurveDialog* dialog = new RemoveCurveDialog(this);
01206 auto prev_curve_count = _curve_list.size();
01207
01208 for(auto& it: _curve_list)
01209 {
01210 dialog->addCurveName( QString::fromStdString( it.first ),
01211 it.second->pen().color() );
01212 }
01213
01214 dialog->exec();
01215
01216 if( prev_curve_count != _curve_list.size() )
01217 {
01218 emit undoableChange();
01219 }
01220 }
01221
01222 void PlotWidget::on_changeColorsDialog_triggered()
01223 {
01224 std::map<std::string,QColor> color_by_name;
01225
01226 for(auto& it: _curve_list)
01227 {
01228 const auto& curve_name = it.first;
01229 auto& curve = it.second;
01230 color_by_name.insert(std::make_pair( curve_name, curve->pen().color() ));
01231 }
01232
01233 CurveColorPick* dialog = new CurveColorPick(color_by_name, this);
01234
01235 connect( dialog, &CurveColorPick::changeColor, this, &PlotWidget::on_changeColor,
01236 Qt::DirectConnection);
01237
01238 dialog->exec();
01239
01240 if( dialog->anyColorModified() )
01241 {
01242 emit undoableChange();
01243 }
01244 }
01245
01246 void PlotWidget::on_changeColor(QString curve_name, QColor new_color)
01247 {
01248 auto it = _curve_list.find(curve_name.toStdString());
01249 if( it != _curve_list.end())
01250 {
01251 auto& curve = it->second;
01252 if( curve->pen().color() != new_color)
01253 {
01254 curve->setPen( new_color, 1.0 );
01255 }
01256 replot();
01257 }
01258 }
01259
01260 void PlotWidget::on_showPoints_triggered()
01261 {
01262 if( _curve_style == QwtPlotCurve::Lines )
01263 {
01264 _curve_style = QwtPlotCurve::LinesAndDots;
01265 }
01266 else if( _curve_style == QwtPlotCurve::LinesAndDots )
01267 {
01268 _curve_style = QwtPlotCurve::Dots;
01269 }
01270 else if( _curve_style == QwtPlotCurve::Dots )
01271 {
01272 _curve_style = QwtPlotCurve::Lines;
01273 }
01274
01275 for(auto& it: _curve_list)
01276 {
01277 auto& curve = it.second;
01278 curve->setPen( curve->pen().color(), (_curve_style == QwtPlotCurve::Dots) ? 4 : 0.8 );
01279 curve->setStyle( _curve_style );
01280 }
01281 replot();
01282 }
01283
01284 void PlotWidget::on_externallyResized(const QRectF& rect)
01285 {
01286 QRectF current_rect = canvasBoundingRect();
01287 if( current_rect == rect)
01288 {
01289 return;
01290 }
01291
01292 if( isXYPlot() )
01293 {
01294 emit undoableChange();
01295 }
01296 else{
01297 emit rectChanged(this, rect);
01298 }
01299 }
01300
01301
01302 void PlotWidget::zoomOut(bool emit_signal)
01303 {
01304 if( _curve_list.size() == 0)
01305 {
01306 QRectF rect(0, 1, 1, -1);
01307 this->setZoomRectangle(rect, false);
01308 return;
01309 }
01310 updateMaximumZoomArea();
01311 setZoomRectangle( _max_zoom_rect, emit_signal);
01312 }
01313
01314 void PlotWidget::on_zoomOutHorizontal_triggered(bool emit_signal)
01315 {
01316 updateMaximumZoomArea();
01317 QRectF act = canvasBoundingRect();
01318 auto rangeX = getMaximumRangeX();
01319
01320 act.setLeft( rangeX.min );
01321 act.setRight( rangeX.max );
01322 this->setZoomRectangle(act, emit_signal);
01323 }
01324
01325 void PlotWidget::on_zoomOutVertical_triggered(bool emit_signal)
01326 {
01327 updateMaximumZoomArea();
01328 QRectF rect = canvasBoundingRect();
01329 auto rangeY = getMaximumRangeY( {rect.left(), rect.right()} );
01330
01331 rect.setBottom( rangeY.min );
01332 rect.setTop( rangeY.max );
01333 this->setZoomRectangle(rect, emit_signal);
01334 }
01335
01336 void PlotWidget::on_changeToBuiltinTransforms(QString new_transform )
01337 {
01338 if( _default_transform == new_transform)
01339 {
01340 return;
01341 }
01342 enableTracker(true);
01343
01344 for(auto& it : _curve_list)
01345 {
01346 const auto& curve_name = it.first;
01347 auto& curve = it.second;
01348
01349 _point_marker[ curve_name ]->setVisible(false);
01350 curve->setTitle( QString::fromStdString( curve_name ) );
01351 _curves_transform[curve_name] = new_transform;
01352
01353 auto data_it = _mapped_data.numeric.find( curve_name );
01354 if( data_it != _mapped_data.numeric.end())
01355 {
01356 const auto& data = data_it->second;
01357 auto data_series = createSeriesData( new_transform, &data);
01358 curve->setData( data_series );
01359 }
01360 }
01361
01362 _default_transform = new_transform;
01363 zoomOut(true);
01364 replot();
01365 }
01366
01367
01368 bool PlotWidget::isXYPlot() const
01369 {
01370 return _axisX && _action_phaseXY->isChecked();
01371 }
01372
01373
01374 void PlotWidget::on_convertToXY_triggered(bool)
01375 {
01376 if( !_axisX )
01377 {
01378 QMessageBox::warning(this, tr("Warning"),
01379 tr("To show a XY plot, you must first provide an alternative X axis.\n"
01380 "You can do this drag'n dropping a curve using the RIGHT mouse button "
01381 "instead of the left mouse button.") );
01382 _action_noTransform->trigger();
01383 return;
01384 }
01385
01386 std::deque<PointSeriesXY*> xy_timeseries;
01387
01388 try{
01389 for(auto& it: _curve_list)
01390 {
01391 const auto& curve_name = it.first;
01392 auto& curve = it.second;
01393 auto& data = _mapped_data.numeric.find(curve_name)->second;
01394 xy_timeseries.push_back( new PointSeriesXY( &data, _axisX) );
01395 _curves_transform[curve_name] = "XYPlot";
01396 }
01397 }
01398 catch(std::exception& ex)
01399 {
01400 QMessageBox::warning(this, tr("Error"), tr(ex.what()) );
01401 _action_noTransform->trigger();
01402 return;
01403 }
01404
01405 enableTracker(false);
01406 _default_transform = "XYPlot";
01407
01408 for(auto& it: _curve_list)
01409 {
01410 const auto& curve_name = it.first;
01411 auto& curve = it.second;
01412 curve->setData( xy_timeseries.front() );
01413 xy_timeseries.pop_front();
01414 _point_marker[ curve_name ]->setVisible(true);
01415 }
01416
01417 QFont font_footer;
01418 font_footer.setPointSize(10);
01419 QwtText text( QString::fromStdString( _axisX->name()) );
01420 text.setFont(font_footer);
01421
01422 this->setFooter( text );
01423
01424 zoomOut(true);
01425 replot();
01426 }
01427
01428
01429 void PlotWidget::updateAvailableTransformers()
01430 {
01431 QSettings settings;
01432 QByteArray xml_text = settings.value("AddCustomPlotDialog.savedXML",
01433 QByteArray() ).toByteArray();
01434 if( !xml_text.isEmpty() )
01435 {
01436 _snippets = GetSnippetsFromXML(xml_text);
01437 }
01438 }
01439
01440 void PlotWidget::transformCustomCurves()
01441 {
01442 std::string error_message;
01443
01444 for (auto& curve_it: _curve_list)
01445 {
01446 auto& curve = curve_it.second;
01447 const auto& curve_name = curve_it.first;
01448 const auto& transform = _curves_transform.at(curve_name);
01449
01450 auto data_it = _mapped_data.numeric.find( curve_name );
01451 if( data_it != _mapped_data.numeric.end())
01452 {
01453 auto& data = data_it->second;
01454 try {
01455 auto data_series = createSeriesData( transform, &data);
01456 curve->setData( data_series );
01457
01458 if( transform == noTransform || transform.isEmpty())
01459 {
01460 curve->setTitle( QString::fromStdString(curve_name) );
01461 }
01462 else{
01463 curve->setTitle( QString::fromStdString(curve_name) + tr(" [") + transform + tr("]") );
01464 }
01465 }
01466 catch (...)
01467 {
01468 _curves_transform[curve_name] = noTransform;
01469 auto data_series = createSeriesData( noTransform, &data);
01470 curve->setData( data_series );
01471
01472 error_message += curve_name + (" [") + transform.toStdString() + ("]\n");
01473
01474 curve->setTitle( QString::fromStdString(curve_name) );
01475 }
01476 }
01477 }
01478 if( error_message.size() > 0)
01479 {
01480 QMessageBox msgBox(this);
01481 msgBox.setWindowTitle("Warnings");
01482 msgBox.setText(tr("Something went wront while creating the following curves. "
01483 "Please check that the transform equation is correct.\n\n") +
01484 QString::fromStdString(error_message) );
01485 msgBox.exec();
01486 }
01487 }
01488
01489 void PlotWidget::on_customTransformsDialog()
01490 {
01491 updateAvailableTransformers();
01492
01493 QStringList available_trans;
01494 for (const auto& it: _snippets)
01495 {
01496 bool valid = true;
01497 QStringList required_channels = CustomFunction::getChannelsFromFuntion( it.second.equation );
01498 for (const auto& channel: required_channels)
01499 {
01500 if( _mapped_data.numeric.count( channel.toStdString() ) == 0)
01501 {
01502 valid = false;
01503 break;
01504 }
01505 }
01506 valid = valid && it.second.equation.contains("value");
01507
01508 if( valid )
01509 {
01510 available_trans.push_back( it.first );
01511 }
01512 }
01513
01514 TransformSelector dialog( builtin_trans, available_trans,
01515 &_default_transform, &_curves_transform,
01516 this);
01517
01518 if (dialog.exec() == QDialog::Rejected )
01519 {
01520 return;
01521 }
01522
01523 transformCustomCurves();
01524 zoomOut(false);
01525 replot();
01526 }
01527
01528 void PlotWidget::changeAxisX(QString curve_name)
01529 {
01530 auto it = _mapped_data.numeric.find( curve_name.toStdString() );
01531 if( it != _mapped_data.numeric.end())
01532 {
01533 _axisX = &(it->second);
01534 _action_phaseXY->trigger();
01535 }
01536 else{
01537
01538 }
01539 }
01540
01541 void PlotWidget::on_savePlotToFile()
01542 {
01543 QString fileName;
01544
01545 QFileDialog saveDialog;
01546 saveDialog.setAcceptMode(QFileDialog::AcceptSave);
01547 saveDialog.setDefaultSuffix("png");
01548
01549 saveDialog.setNameFilter("Compatible formats (*.jpg *.jpeg *.png)");
01550
01551 saveDialog.exec();
01552
01553 if(saveDialog.result() == QDialog::Accepted && !saveDialog.selectedFiles().empty())
01554 {
01555 fileName = saveDialog.selectedFiles().first();
01556
01557 QPixmap pixmap (1200,900);
01558 QPainter * painter = new QPainter(&pixmap);
01559
01560 if ( !fileName.isEmpty() )
01561 {
01562 QwtPlotRenderer rend;
01563 rend.render(this, painter, QRect(0, 0, pixmap.width(), pixmap.height()));
01564 pixmap.save(fileName);
01565 }
01566 }
01567 }
01568
01569 void PlotWidget::on_editAxisLimits_triggered()
01570 {
01571 auto rangeX = this->getMaximumRangeX();
01572
01573
01574 _custom_Y_limits.min = -MAX_DOUBLE;
01575 _custom_Y_limits.max = MAX_DOUBLE;
01576
01577 auto rangeY = getMaximumRangeY(rangeX);
01578
01579 _axis_limits_dialog->setDefaultRange(rangeY);
01580 _axis_limits_dialog->exec();
01581
01582 _custom_Y_limits = _axis_limits_dialog->rangeY();
01583
01584 on_zoomOutVertical_triggered(false);
01585 replot();
01586 emit undoableChange();
01587 }
01588
01589
01590 bool PlotWidget::eventFilter(QObject *obj, QEvent *event)
01591 {
01592 QwtScaleWidget *bottomAxis = this->axisWidget(xBottom);
01593 QwtScaleWidget *leftAxis = this->axisWidget(yLeft);
01594
01595 if( _magnifier && (obj == bottomAxis || obj == leftAxis)
01596 && !(isXYPlot() && _keep_aspect_ratio ) )
01597 {
01598 if( event->type() == QEvent::Wheel)
01599 {
01600 auto wheel_event = dynamic_cast<QWheelEvent*>(event);
01601 if( obj == bottomAxis ) {
01602 _magnifier->setDefaultMode( PlotMagnifier::X_AXIS);
01603 }
01604 else{
01605 _magnifier->setDefaultMode( PlotMagnifier::Y_AXIS );
01606 }
01607 _magnifier->widgetWheelEvent(wheel_event);
01608 }
01609 }
01610
01611 if( obj == canvas() )
01612 {
01613 if( _magnifier ){
01614 _magnifier->setDefaultMode( PlotMagnifier::BOTH_AXES);
01615 }
01616 return canvasEventFilter(event);
01617 }
01618
01619 return false;
01620 }
01621
01622 bool PlotWidget::canvasEventFilter(QEvent *event)
01623 {
01624 switch( event->type() )
01625 {
01626 case QEvent::Wheel:
01627 {
01628 auto mouse_event = dynamic_cast<QWheelEvent*>(event);
01629
01630 bool ctrl_modifier = mouse_event->modifiers() == Qt::ControlModifier;
01631 auto legend_rect = _legend->geometry( canvas()->rect() );
01632
01633 if ( ctrl_modifier)
01634 {
01635 if( legend_rect.contains( mouse_event->pos() )
01636 && _legend->isVisible() )
01637 {
01638 int point_size = _legend->font().pointSize();
01639 if( mouse_event->delta() > 0 && point_size < 12)
01640 {
01641 emit legendSizeChanged(point_size+1);
01642 }
01643 if( mouse_event->delta() < 0 && point_size > 6)
01644 {
01645 emit legendSizeChanged(point_size-1);
01646 }
01647 return true;
01648 }
01649 }
01650
01651 return false;
01652 }
01653
01654 case QEvent::MouseButtonPress:
01655 {
01656 if( _dragging.mode != DragInfo::NONE)
01657 {
01658 return true;
01659 }
01660
01661 QMouseEvent *mouse_event = static_cast<QMouseEvent*>(event);
01662
01663 if( mouse_event->button() == Qt::LeftButton )
01664 {
01665 const QPoint press_point = mouse_event->pos();
01666 if( mouse_event->modifiers() == Qt::ShiftModifier)
01667 {
01668 QPointF pointF ( invTransform( xBottom, press_point.x()),
01669 invTransform( yLeft, press_point.y()) );
01670 emit trackerMoved(pointF);
01671 return true;
01672 }
01673 else if( mouse_event->modifiers() == Qt::ControlModifier)
01674 {
01675 QApplication::setOverrideCursor(QCursor(QPixmap(":/icons/resources/light/move.png")));
01676 }
01677 else{
01678 auto clicked_item = _legend->processMousePressEvent(mouse_event);
01679 if( clicked_item )
01680 {
01681 for( const auto& curve_it: _curve_list)
01682 {
01683 if( clicked_item == curve_it.second)
01684 {
01685 auto &curve = _curve_list.at( curve_it.first );
01686 curve->setVisible( !curve->isVisible() );
01687 _tracker->redraw();
01688 replot();
01689 return true;
01690 }
01691 }
01692 }
01693 }
01694 return false;
01695 }
01696 else if ( mouse_event->buttons() == Qt::MidButton &&
01697 mouse_event->modifiers() == Qt::NoModifier )
01698 {
01699 QApplication::setOverrideCursor(QCursor(QPixmap(":/icons/resource/lights/move.png")));
01700 return false;
01701 }
01702 else if( mouse_event->button() == Qt::RightButton )
01703 {
01704 if( mouse_event->modifiers() == Qt::NoModifier)
01705 {
01706 canvasContextMenuTriggered( mouse_event->pos() );
01707 return true;
01708 }
01709 else if( mouse_event->modifiers() == Qt::ControlModifier)
01710 {
01711
01712 QDrag *drag = new QDrag(this);
01713 QMimeData *mimeData = new QMimeData;
01714
01715 QByteArray data;
01716 QDataStream dataStream(&data, QIODevice::WriteOnly);
01717
01718 dataStream << this->windowTitle();
01719
01720 mimeData->setData("plot_area", data );
01721 drag->setMimeData(mimeData);
01722 drag->exec();
01723
01724 return true;
01725 }
01726 }
01727 }break;
01728
01729 case QEvent::MouseMove:
01730 {
01731 if( _dragging.mode != DragInfo::NONE)
01732 {
01733 return true;
01734 }
01735
01736 QMouseEvent *mouse_event = static_cast<QMouseEvent*>(event);
01737
01738 if ( mouse_event->buttons() == Qt::LeftButton &&
01739 mouse_event->modifiers() == Qt::ShiftModifier )
01740 {
01741 const QPoint point = mouse_event->pos();
01742 QPointF pointF ( invTransform( xBottom, point.x()),
01743 invTransform( yLeft, point.y()) );
01744 emit trackerMoved(pointF);
01745 return true;
01746 }
01747 }break;
01748
01749 case QEvent::Leave:
01750 {
01751 if( _dragging.mode == DragInfo::NONE )
01752 {
01753 changeBackgroundColor( Qt::white );
01754 QApplication::restoreOverrideCursor();
01755 return false;
01756 }
01757 }break;
01758 case QEvent::MouseButtonRelease :
01759 {
01760 if( _dragging.mode == DragInfo::NONE )
01761 {
01762 changeBackgroundColor( Qt::white );
01763 QApplication::restoreOverrideCursor();
01764 return false;
01765 }
01766 }break;
01767
01768 case QEvent::Enter:
01769 {
01770
01771
01772
01773 dropEvent(nullptr);
01774 return true;
01775 }break;
01776
01777 default: {}
01778
01779 }
01780
01781 return false;
01782 }
01783
01784 void PlotWidget::setDefaultRangeX()
01785 {
01786 if( _mapped_data.numeric.size() > 0)
01787 {
01788 double min = std::numeric_limits<double>::max();
01789 double max = -std::numeric_limits<double>::max();
01790 for (auto& it: _mapped_data.numeric )
01791 {
01792 const PlotData& data = it.second;
01793 if( data.size() > 0){
01794 double A = data.front().x;
01795 double B = data.back().x;
01796 min = std::min( A, min );
01797 max = std::max( B, max );
01798 }
01799 }
01800 this->setAxisScale( xBottom, min - _time_offset, max - _time_offset);
01801 }
01802 }
01803
01804 DataSeriesBase *PlotWidget::createSeriesData(const QString &ID, const PlotData *data)
01805 {
01806 DataSeriesBase *output = nullptr;
01807
01808 if(ID.isEmpty() || ID == noTransform)
01809 {
01810 output = new Timeseries_NoTransform( data );
01811 }
01812 else if( ID == Derivative1st || ID == "firstDerivative")
01813 {
01814 output = new Timeseries_1stDerivative( data );
01815 }
01816 else if( ID == Derivative2nd || ID == "secondDerivative")
01817 {
01818 output = new Timeseries_2ndDerivative( data );
01819 }
01820 if( ID == "XYPlot")
01821 {
01822 try {
01823 output = new PointSeriesXY( data, _axisX );
01824 }
01825 catch (std::runtime_error& ex)
01826 {
01827 if( if_xy_plot_failed_show_dialog )
01828 {
01829 QMessageBox msgBox(this);
01830 msgBox.setWindowTitle("Warnings");
01831 msgBox.setText( tr("The creation of the XY plot failed with the following message:\n %1")
01832 .arg( ex.what()) );
01833
01834
01835
01836 msgBox.addButton("Continue", QMessageBox::AcceptRole);
01837 msgBox.exec();
01838
01839
01840
01841
01842
01843 }
01844 throw std::runtime_error("Creation of XY plot failed");
01845 }
01846 }
01847 auto custom_it = _snippets.find(ID);
01848 if( custom_it != _snippets.end())
01849 {
01850 const auto& snippet = custom_it->second;
01851 output = new CustomTimeseries( data, snippet, _mapped_data );
01852 }
01853
01854 if( !output ){
01855 throw std::runtime_error("Not recognized ID in createSeriesData: ");
01856 }
01857 output->setTimeOffset( _time_offset );
01858 return output;
01859 }
01860
01861 void PlotWidget::changeBackgroundColor(QColor color)
01862 {
01863 if( canvasBackground().color() != color)
01864 {
01865 setCanvasBackground( color );
01866 replot();
01867 }
01868 }
01869
01870 void PlotWidget::setLegendSize(int size)
01871 {
01872 auto font = _legend->font();
01873 font.setPointSize( size );
01874 _legend->setFont( font );
01875 replot();
01876 }
01877
01878 bool PlotWidget::isLegendVisible() const
01879 {
01880 return _legend && _legend->isVisible();
01881 }
01882
01883 void PlotWidget::setLegendAlignment(Qt::Alignment alignment)
01884 {
01885 _legend->setAlignment( Qt::Alignment( Qt::AlignTop | alignment ) );
01886 }
01887
01888 void PlotWidget::setZoomEnabled(bool enabled)
01889 {
01890 _zoom_enabled = enabled;
01891 _zoomer->setEnabled( enabled );
01892 _magnifier->setEnabled( enabled );
01893 _panner1->setEnabled( enabled );
01894 _panner2->setEnabled( enabled );
01895 }
01896
01897 bool PlotWidget::isZoomEnabled() const
01898 {
01899 return _zoom_enabled;
01900 }
01901
01902
01903
01904 void PlotWidget::replot()
01905 {
01906 static int replot_count = 0;
01907
01908 if( _zoomer ){
01909 _zoomer->setZoomBase( false );
01910 }
01911
01912 QwtPlot::replot();
01913
01914 }
01915