plotwidget.cpp
Go to the documentation of this file.
00001 #include "plotwidget.h"
00002 #include <QDebug>
00003 #include <QDrag>
00004 #include <QMimeData>
00005 #include <QDragEnterEvent>
00006 #include <qwt_plot_canvas.h>
00007 #include <qwt_scale_engine.h>
00008 #include <qwt_plot_layout.h>
00009 #include <qwt_scale_draw.h>
00010 #include <QAction>
00011 #include <QMessageBox>
00012 #include <QMenu>
00013 #include <limits>
00014 #include "removecurvedialog.h"
00015 #include "curvecolorpick.h"
00016 #include <QApplication>
00017 #include <set>
00018 #include <memory>
00019 #include <qwt_text.h>
00020 #include <QActionGroup>
00021 #include <QFileDialog>
00022 #include <QtXml/QDomElement>
00023 #include "qwt_plot_renderer.h"
00024 #include "PlotJuggler/random_color.h"
00025 
00026 const double MAX_DOUBLE = std::numeric_limits<double>::max() / 2 ;
00027 
00028 void PlotWidget::setDefaultRangeX()
00029 {
00030     if( _mapped_data.numeric.size() > 0)
00031     {
00032         double min =  std::numeric_limits<double>::max();
00033         double max = -std::numeric_limits<double>::max();
00034         for (auto it: _mapped_data.numeric )
00035         {
00036             const PlotDataPtr& data = it.second;
00037             if( data->size() > 0){
00038                 double A = data->at(0).x;
00039                 double B = data->at( data->size() -1 ).x;
00040                 if( A < min) min = A;
00041                 if( B > max) max = B;
00042             }
00043         }
00044         this->setAxisScale( xBottom, min - _time_offset, max - _time_offset);
00045     }
00046 }
00047 
00048 PlotWidget::PlotWidget(PlotDataMap &datamap, QWidget *parent):
00049     QwtPlot(parent),
00050     _zoomer( 0 ),
00051     _magnifier(0 ),
00052     _panner( 0 ),
00053     _tracker ( 0 ),
00054     _legend( 0 ),
00055     _grid( 0 ),
00056     _mapped_data( datamap ),
00057     _show_line_and_points(false),
00058     _current_transform( TimeseriesQwt::noTransform ),
00059     _time_offset(0.0)
00060 {
00061     this->setAcceptDrops( true );
00062     this->setMinimumWidth( 100 );
00063     this->setMinimumHeight( 100 );
00064 
00065     this->sizePolicy().setHorizontalPolicy( QSizePolicy::Expanding);
00066     this->sizePolicy().setVerticalPolicy( QSizePolicy::Expanding);
00067 
00068     QwtPlotCanvas *canvas = new QwtPlotCanvas(this);
00069 
00070     canvas->setFrameStyle( QFrame::NoFrame );
00071     canvas->setPaintAttribute( QwtPlotCanvas::BackingStore, true );
00072 
00073     this->setCanvas( canvas );
00074     this->setCanvasBackground( QColor( 250, 250, 250 ) );
00075     this->setAxisAutoScale(0, true);
00076 
00077     this->axisScaleEngine(QwtPlot::xBottom)->setAttribute(QwtScaleEngine::Floating,true);
00078     this->plotLayout()->setAlignCanvasToScales( true );
00079 
00080     this->canvas()->installEventFilter( this );
00081 
00082     //--------------------------
00083     _grid = new QwtPlotGrid();
00084     _zoomer = ( new PlotZoomer( this->canvas() ) );
00085     _magnifier = ( new PlotMagnifier( this->canvas() ) );
00086     _panner = ( new QwtPlotPanner( this->canvas() ) );
00087     _tracker = ( new CurveTracker( this ) );
00088 
00089     _grid->setPen(QPen(Qt::gray, 0.0, Qt::DotLine));
00090 
00091     _zoomer->setRubberBandPen( QColor( Qt::red , 1, Qt::DotLine) );
00092     _zoomer->setTrackerPen( QColor( Qt::green, 1, Qt::DotLine ) );
00093     _zoomer->setMousePattern( QwtEventPattern::MouseSelect1, Qt::LeftButton, Qt::NoModifier );
00094     connect(_zoomer,  &PlotZoomer::zoomed, this, &PlotWidget::on_externallyResized );
00095 
00096     _magnifier->setAxisEnabled(xTop, false);
00097     _magnifier->setAxisEnabled(yRight, false);
00098 
00099     // disable right button. keep mouse wheel
00100     _magnifier->setMouseButton( Qt::NoButton );
00101     connect(_magnifier, &PlotMagnifier::rescaled, this, &PlotWidget::on_externallyResized );
00102     connect(_magnifier, &PlotMagnifier::rescaled, this, &PlotWidget::replot );
00103 
00104     _panner->setMouseButton(  Qt::LeftButton, Qt::ControlModifier);
00105 
00106     //-------------------------
00107 
00108     buildActions();
00109     buildLegend();
00110 
00111     this->canvas()->setMouseTracking(true);
00112     this->canvas()->installEventFilter(this);
00113 
00114     setDefaultRangeX();
00115 
00116     _axis_limits_dialog = new AxisLimitsDialog(this);
00117 
00118     _custom_Y_limits.min = (-MAX_DOUBLE );
00119     _custom_Y_limits.max = ( MAX_DOUBLE );
00120 
00121 }
00122 
00123 void PlotWidget::buildActions()
00124 {
00125     _action_removeCurve = new QAction(tr("&Remove curves"), this);
00126     _action_removeCurve->setStatusTip(tr("Remove one or more curves from this plot"));
00127     connect(_action_removeCurve, &QAction::triggered, this, &PlotWidget::launchRemoveCurveDialog);
00128 
00129     QIcon iconDelete;
00130     iconDelete.addFile(QStringLiteral(":/icons/resources/checkboxalt.png"), QSize(26, 26));
00131     _action_removeAllCurves = new QAction(tr("&Remove all curves"), this);
00132     _action_removeAllCurves->setIcon(iconDelete);
00133     connect(_action_removeAllCurves, &QAction::triggered, this, &PlotWidget::detachAllCurves);
00134     connect(_action_removeAllCurves, &QAction::triggered, this, &PlotWidget::undoableChange );
00135 
00136     QIcon iconColors;
00137     iconColors.addFile(QStringLiteral(":/icons/resources/office_chart_lines.png"), QSize(26, 26));
00138     _action_changeColorsDialog = new QAction(tr("&Change colors"), this);
00139     _action_changeColorsDialog->setIcon(iconColors);
00140     _action_changeColorsDialog->setStatusTip(tr("Change the color of the curves"));
00141     connect(_action_changeColorsDialog, &QAction::triggered, this, &PlotWidget::on_changeColorsDialog_triggered);
00142 
00143     QIcon iconPoints;
00144     iconPoints.addFile(QStringLiteral(":/icons/resources/line_chart_32px.png"), QSize(26, 26));
00145     _action_showPoints = new QAction(tr("&Show lines and points"), this);
00146     _action_showPoints->setIcon(iconPoints);
00147     _action_showPoints->setCheckable( true );
00148     _action_showPoints->setChecked( false );
00149     connect(_action_showPoints, &QAction::triggered, this, &PlotWidget::on_showPoints_triggered);
00150 
00151     _action_editLimits = new  QAction(tr("&Edit Axis Limits"), this);
00152     connect(_action_editLimits, &QAction::triggered, this, &PlotWidget::on_editAxisLimits_triggered);
00153 
00154     QIcon iconZoomH;
00155     iconZoomH.addFile(QStringLiteral(":/icons/resources/resize_horizontal.png"), QSize(26, 26));
00156     _action_zoomOutHorizontally = new QAction(tr("&Zoom Out Horizontally"), this);
00157     _action_zoomOutHorizontally->setIcon(iconZoomH);
00158     connect(_action_zoomOutHorizontally, &QAction::triggered, this, &PlotWidget::on_zoomOutHorizontal_triggered);
00159     connect(_action_zoomOutHorizontally, &QAction::triggered, this, &PlotWidget::undoableChange );
00160 
00161     QIcon iconZoomV;
00162     iconZoomV.addFile(QStringLiteral(":/icons/resources/resize_vertical.png"), QSize(26, 26));
00163     _action_zoomOutVertically = new QAction(tr("&Zoom Out Vertically"), this);
00164     _action_zoomOutVertically->setIcon(iconZoomV);
00165     connect(_action_zoomOutVertically, &QAction::triggered, this, &PlotWidget::on_zoomOutVertical_triggered);
00166     connect(_action_zoomOutVertically, &QAction::triggered, this, &PlotWidget::undoableChange );
00167 
00168     _action_noTransform = new QAction(tr("&NO Transform"), this);
00169     _action_noTransform->setCheckable( true );
00170     _action_noTransform->setChecked( true );
00171     connect(_action_noTransform, &QAction::triggered, this, &PlotWidget::on_noTransform_triggered);
00172 
00173     _action_1stDerivativeTransform = new QAction(tr("&1st derivative"), this);
00174     _action_1stDerivativeTransform->setCheckable( true );
00175     connect(_action_1stDerivativeTransform, &QAction::triggered, this, &PlotWidget::on_1stDerivativeTransform_triggered);
00176 
00177     _action_2ndDerivativeTransform = new QAction(tr("&2nd Derivative"), this);
00178     _action_2ndDerivativeTransform->setCheckable( true );
00179     connect(_action_2ndDerivativeTransform, &QAction::triggered, this, &PlotWidget::on_2ndDerivativeTransform_triggered);
00180 
00181     _action_phaseXY = new QAction(tr("&XY plot"), this);
00182     _action_phaseXY->setCheckable( true );
00183     connect(_action_phaseXY, &QAction::triggered, this, &PlotWidget::on_convertToXY_triggered);
00184 
00185     QIcon iconSave;
00186     iconSave.addFile(QStringLiteral(":/icons/resources/filesave@2x.png"), QSize(26, 26));
00187     _action_saveToFile = new  QAction(tr("&Save plot to file"), this);
00188     _action_saveToFile->setIcon(iconSave);
00189     connect(_action_saveToFile, &QAction::triggered, this, &PlotWidget::on_savePlotToFile);
00190 
00191     auto transform_group = new QActionGroup(this);
00192 
00193     transform_group->addAction(_action_noTransform);
00194     transform_group->addAction(_action_1stDerivativeTransform);
00195     transform_group->addAction(_action_2ndDerivativeTransform);
00196     transform_group->addAction(_action_phaseXY);
00197 }
00198 
00199 
00200 void PlotWidget::canvasContextMenuTriggered(const QPoint &pos)
00201 {
00202     QString edit("&Edit Axis Limits ");
00203     edit.append( _axis_limits_dialog->limitsEnabled() ? tr("(ENABLED)") : tr("(disabled)") ) ;
00204     _action_editLimits->setText( edit );
00205 
00206     QMenu menu(this);
00207     menu.addAction(_action_removeCurve);
00208     menu.addAction(_action_removeAllCurves);
00209     menu.addSeparator();
00210     menu.addAction(_action_changeColorsDialog);
00211     menu.addAction(_action_showPoints);
00212     menu.addSeparator();
00213     menu.addAction(_action_editLimits);
00214     menu.addAction(_action_zoomOutHorizontally);
00215     menu.addAction(_action_zoomOutVertically);
00216     menu.addSeparator();
00217     menu.addAction( _action_noTransform );
00218     menu.addAction( _action_1stDerivativeTransform );
00219     menu.addAction( _action_2ndDerivativeTransform );
00220     menu.addAction( _action_phaseXY );
00221     menu.addSeparator();
00222     menu.addAction( _action_saveToFile );
00223 
00224     _action_removeCurve->setEnabled( ! _curve_list.empty() );
00225     _action_removeAllCurves->setEnabled( ! _curve_list.empty() );
00226     _action_changeColorsDialog->setEnabled(  ! _curve_list.empty() );
00227 
00228     menu.exec( canvas()->mapToGlobal(pos) );
00229 }
00230 
00231 
00232 void PlotWidget::buildLegend()
00233 {
00234     _legend = new QwtPlotLegendItem();
00235     _legend->attach( this );
00236 
00237     _legend->setRenderHint( QwtPlotItem::RenderAntialiased );
00238     QColor color( Qt::black );
00239     _legend->setTextPen( color );
00240     _legend->setBorderPen( color );
00241     QColor c( Qt::white );
00242     c.setAlpha( 200 );
00243     _legend->setBackgroundBrush( c );
00244 
00245     _legend->setMaxColumns( 1 );
00246     _legend->setAlignment( Qt::Alignment( Qt::AlignTop | Qt::AlignRight ) );
00247     _legend->setBackgroundMode( QwtPlotLegendItem::BackgroundMode::LegendBackground   );
00248 
00249     _legend->setBorderRadius( 6 );
00250     _legend->setMargin( 1 );
00251     _legend->setSpacing( 0 );
00252     _legend->setItemMargin( 0 );
00253 
00254     QFont font = _legend->font();
00255     font.setPointSize( 8 );
00256     _legend->setFont( font );
00257     _legend->setVisible( true );
00258 }
00259 
00260 
00261 
00262 PlotWidget::~PlotWidget()
00263 {
00264 
00265 }
00266 
00267 bool PlotWidget::addCurve(const QString &name, bool do_replot)
00268 {
00269     auto it = _mapped_data.numeric.find( name.toStdString() );
00270     if( it == _mapped_data.numeric.end())
00271     {
00272         return false;
00273     }
00274 
00275     if( _curve_list.find(name) != _curve_list.end())
00276     {
00277         return false;
00278     }
00279 
00280     PlotDataPtr data = it->second;
00281 
00282     {
00283         auto curve = std::shared_ptr< QwtPlotCurve >( new QwtPlotCurve(name) );
00284 
00285         TimeseriesQwt* plot_qwt = new TimeseriesQwt( data );
00286         plot_qwt->setTimeOffset( _time_offset );
00287 
00288         curve->setPaintAttribute( QwtPlotCurve::ClipPolygons, true );
00289         curve->setPaintAttribute( QwtPlotCurve::FilterPointsAggressive, true );
00290 
00291         plot_qwt->setAlternativeAxisX( _axisX );
00292 
00293         if( _current_transform != TimeseriesQwt::noTransform)
00294         {
00295             plot_qwt->setTransform( _current_transform );
00296         }
00297 
00298         curve->setData( plot_qwt );
00299 
00300         if( _show_line_and_points ) {
00301             curve->setStyle( QwtPlotCurve::LinesAndDots);
00302         }
00303         else{
00304             curve->setStyle( QwtPlotCurve::Lines);
00305         }
00306 
00307         QColor color = data->getColorHint();
00308         if( color == Qt::black)
00309         {
00310             color = randomColorHint();
00311             data->setColorHint(color);
00312         }
00313         curve->setPen( color,  0.8 );
00314         curve->setRenderHint( QwtPlotItem::RenderAntialiased, true );
00315 
00316         curve->attach( this );
00317         _curve_list.insert( std::make_pair(name, curve));
00318 
00319         auto marker = new QwtPlotMarker;
00320         _point_marker.insert( std::make_pair(name, marker) );
00321         marker->attach( this );
00322         marker->setVisible( isXYPlot() );
00323 
00324         QwtSymbol *sym = new QwtSymbol(
00325                     QwtSymbol::Diamond,
00326                     Qt::red, color,
00327                     QSize(10,10));
00328 
00329         marker->setSymbol(sym);
00330     }
00331 
00332     zoomOut(false);
00333 
00334     if( do_replot )
00335     {
00336         replot();
00337     }
00338 
00339     return true;
00340 }
00341 
00342 void PlotWidget::removeCurve(const QString &name)
00343 {
00344     auto it = _curve_list.find(name);
00345     if( it != _curve_list.end() )
00346     {
00347         auto curve = it->second;
00348         curve->detach();
00349         replot();
00350         _curve_list.erase( it );
00351 
00352         _point_marker[name]->detach();
00353         _point_marker.erase( name );
00354     }
00355     if( isXYPlot() && _axisX->name() == name.toStdString())
00356     {
00357         _axisX = PlotDataPtr();
00358         for(auto it : _curve_list)
00359         {
00360             TimeseriesQwt* series = static_cast<TimeseriesQwt*>( it.second->data() );
00361             series->setAlternativeAxisX(_axisX);
00362         }
00363         _action_noTransform->trigger();
00364     }
00365 }
00366 
00367 bool PlotWidget::isEmpty() const
00368 {
00369     return _curve_list.empty();
00370 }
00371 
00372 const std::map<QString, std::shared_ptr<QwtPlotCurve> > &PlotWidget::curveList() const
00373 {
00374     return _curve_list;
00375 }
00376 
00377 void PlotWidget::dragEnterEvent(QDragEnterEvent *event)
00378 {
00379     QwtPlot::dragEnterEvent(event);
00380 
00381     const QMimeData *mimeData = event->mimeData();
00382     QStringList mimeFormats = mimeData->formats();
00383     for(const QString& format: mimeFormats)
00384     {
00385         QByteArray encoded = mimeData->data( format );
00386         QDataStream stream(&encoded, QIODevice::ReadOnly);
00387 
00388         if( format.contains( "curveslist") )
00389         {
00390             event->acceptProposedAction();
00391         }
00392         if( format.contains( "plot_area")  )
00393         {
00394             QString source_name;
00395             stream >> source_name;
00396 
00397             if(QString::compare( windowTitle(),source_name ) != 0 ){
00398                 event->acceptProposedAction();
00399             }
00400         }
00401     }
00402 }
00403 void PlotWidget::dragMoveEvent(QDragMoveEvent *)
00404 {
00405 
00406 }
00407 
00408 
00409 void PlotWidget::dropEvent(QDropEvent *event)
00410 {
00411     QwtPlot::dropEvent(event);
00412 
00413     const QMimeData *mimeData = event->mimeData();
00414     QStringList mimeFormats = mimeData->formats();
00415 
00416     for(const QString& format: mimeFormats)
00417     {
00418         QByteArray encoded = mimeData->data( format );
00419         QDataStream stream(&encoded, QIODevice::ReadOnly);
00420 
00421         if( format.contains( "curveslist/add_curve") )
00422         {
00423             bool plot_added = false;
00424             while (!stream.atEnd())
00425             {
00426                 QString curve_name;
00427                 stream >> curve_name;
00428                 addCurve( curve_name, true );
00429                 plot_added = true;
00430             }
00431             if( plot_added ) {
00432                 emit undoableChange();
00433             }
00434         }
00435         else if( format.contains( "curveslist/new_X_axis") )
00436         {
00437             QString curve_name;
00438             stream >> curve_name;
00439             changeAxisX(curve_name);
00440         }
00441         else if( format.contains( "plot_area") )
00442         {
00443             QString source_name;
00444             stream >> source_name;
00445             PlotWidget* source_plot = static_cast<PlotWidget*>( event->source() );
00446             emit swapWidgetsRequested( source_plot, this );
00447         }
00448     }
00449 }
00450 
00451 void PlotWidget::detachAllCurves()
00452 {
00453     for(auto it: _curve_list)   { it.second->detach(); }
00454     for(auto it: _point_marker) { it.second->detach(); }
00455 
00456     if( isXYPlot() )
00457     {
00458         _axisX = PlotDataPtr();
00459         _action_noTransform->trigger();
00460     }
00461 
00462     _curve_list.erase(_curve_list.begin(), _curve_list.end());
00463     _point_marker.erase(_point_marker.begin(), _point_marker.end());
00464     emit _tracker->setPosition( _tracker->actualPosition() );
00465     replot();
00466 }
00467 
00468 QDomElement PlotWidget::xmlSaveState( QDomDocument &doc) const
00469 {
00470     QDomElement plot_el = doc.createElement("plot");
00471 
00472     QDomElement range_el = doc.createElement("range");
00473     QRectF rect = this->currentBoundingRect();
00474     range_el.setAttribute("bottom", QString::number(rect.bottom(), 'f', 6) );
00475     range_el.setAttribute("top", QString::number(rect.top(), 'f', 6));
00476     range_el.setAttribute("left", QString::number(rect.left(), 'f', 6));
00477     range_el.setAttribute("right", QString::number(rect.right() ,'f', 6));
00478     plot_el.appendChild(range_el);
00479 
00480     QDomElement limitY_el = doc.createElement("limitY");
00481     if( _custom_Y_limits.min > -MAX_DOUBLE){
00482         limitY_el.setAttribute("min", QString::number( _custom_Y_limits.min) );
00483     }
00484     if( _custom_Y_limits.max < MAX_DOUBLE){
00485         limitY_el.setAttribute("max", QString::number( _custom_Y_limits.max) );
00486     }
00487     plot_el.appendChild(limitY_el);
00488 
00489     for(auto it=_curve_list.begin(); it != _curve_list.end(); ++it)
00490     {
00491         QString name = it->first;
00492         auto curve = it->second;
00493         QDomElement curve_el = doc.createElement("curve");
00494         curve_el.setAttribute( "name",name);
00495         curve_el.setAttribute( "R", curve->pen().color().red());
00496         curve_el.setAttribute( "G", curve->pen().color().green());
00497         curve_el.setAttribute( "B", curve->pen().color().blue());
00498 
00499         plot_el.appendChild(curve_el);
00500     }
00501 
00502     QDomElement transform  = doc.createElement("transform");
00503 
00504     switch(_current_transform)
00505     {
00506     case TimeseriesQwt::firstDerivative:
00507         transform.setAttribute("value", "firstDerivative" ); break;
00508 
00509     case TimeseriesQwt::secondDerivative:
00510         transform.setAttribute("value", "secondDerivative" ); break;
00511 
00512     case TimeseriesQwt::noTransform:
00513         transform.setAttribute("value", "noTransform" ); break;
00514 
00515     case TimeseriesQwt::XYPlot:{
00516 
00517         if( _axisX ){
00518             transform.setAttribute("value", "XYPlot" );
00519             transform.setAttribute("axisX",  _axisX->name().c_str() );
00520         }
00521         else{
00522             transform.setAttribute("value", "noTransform" );
00523             transform.setAttribute("axisX",  "" );
00524         }
00525     }break;
00526 
00527     }
00528     plot_el.appendChild(transform);
00529 
00530     return plot_el;
00531 }
00532 
00533 bool PlotWidget::xmlLoadState(QDomElement &plot_widget, QMessageBox::StandardButton* answer)
00534 {
00535     QDomElement curve;
00536 
00537     std::set<QString> added_curve_names;
00538 
00539     QDomElement transform = plot_widget.firstChildElement( "transform" );
00540     if( !transform.isNull()    )
00541     {
00542         if( transform.attribute("value") == "XYPlot")
00543         {
00544             QString axisX_name = transform.attribute("axisX");
00545             if( axisX_name.size()>0){
00546                 changeAxisX( axisX_name );
00547             }
00548         }
00549     }
00550 
00551     QDomElement limitY_el = plot_widget.firstChildElement("limitY");
00552     if( !limitY_el.isNull() )
00553     {
00554         if( limitY_el.hasAttribute("min") ) {
00555             _custom_Y_limits.min = limitY_el.attribute("min").toDouble();
00556             _axis_limits_dialog->enableMin( true, _custom_Y_limits.min);
00557         }
00558         else{
00559             _custom_Y_limits.max = -MAX_DOUBLE;
00560             _axis_limits_dialog->enableMin( false, _custom_Y_limits.min);
00561         }
00562 
00563         if( limitY_el.hasAttribute("max") ) {
00564             _custom_Y_limits.max = limitY_el.attribute("max").toDouble();
00565             _axis_limits_dialog->enableMax( true, _custom_Y_limits.max);
00566         }
00567         else{
00568             _custom_Y_limits.max  = MAX_DOUBLE;
00569             _axis_limits_dialog->enableMax( false, _custom_Y_limits.max);
00570         }
00571     }
00572 
00573     for (  curve = plot_widget.firstChildElement( "curve" )  ;
00574            !curve.isNull();
00575            curve = curve.nextSiblingElement( "curve" ) )
00576     {
00577         QString curve_name = curve.attribute("name");
00578         int R = curve.attribute("R").toInt();
00579         int G = curve.attribute("G").toInt();
00580         int B = curve.attribute("B").toInt();
00581         QColor color(R,G,B);
00582 
00583         if(  _mapped_data.numeric.find(curve_name.toStdString()) != _mapped_data.numeric.end() )
00584         {
00585             addCurve(curve_name, false);
00586             _curve_list[curve_name]->setPen( color, 1.0);
00587             added_curve_names.insert(curve_name );
00588         }
00589         else{
00590             if( *answer !=  QMessageBox::YesToAll)
00591             {
00592                 *answer = QMessageBox::question(
00593                             0,
00594                             tr("Warning"),
00595                             tr("Can't find the curve with name %1.\n Do you want to ignore it? ").arg(curve_name),
00596                             QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::Abort ,
00597                             QMessageBox::Abort );
00598             }
00599 
00600             if( *answer ==  QMessageBox::Yes || *answer ==  QMessageBox::YesToAll) {
00601                 continue;
00602             }
00603 
00604             if( *answer ==  QMessageBox::Abort) {
00605                 return false;
00606             }
00607         }
00608     }
00609 
00610     bool curve_removed = true;
00611 
00612     while( curve_removed)
00613     {
00614         curve_removed = false;
00615         for(auto& it: _curve_list)
00616         {
00617             QString curve_name = it.first;
00618             if( added_curve_names.find( curve_name ) == added_curve_names.end())
00619             {
00620                 removeCurve( curve_name );
00621                 curve_removed = true;
00622                 break;
00623             }
00624         }
00625     }
00626 
00627     //-----------------------------------------
00628 
00629     if( !transform.isNull()  )
00630     {
00631         QString trans_value = transform.attribute("value");
00632         if( trans_value == "firstDerivative")
00633         {
00634             _action_1stDerivativeTransform->trigger();
00635         }
00636         else if(trans_value == "secondDerivative")
00637         {
00638             _action_2ndDerivativeTransform->trigger();
00639         }
00640         else if(trans_value == "noTransform")
00641         {
00642             _action_noTransform->trigger();
00643         }
00644 //        else if(trans_value == "XYPlot")
00645 //        {
00646 //            QString axisX_name = transform.attribute("axisX");
00647 //            if( axisX_name.size()>0)
00648 //            {
00649 //                changeAxisX( axisX_name );
00650 //            }
00651 //        }
00652     }
00653     //-----------------------------------------
00654 
00655     QDomElement rectangle = plot_widget.firstChildElement( "range" );
00656     if( !rectangle.isNull()){
00657         QRectF rect;
00658         rect.setBottom( rectangle.attribute("bottom").toDouble());
00659         rect.setTop( rectangle.attribute("top").toDouble());
00660         rect.setLeft( rectangle.attribute("left").toDouble());
00661         rect.setRight( rectangle.attribute("right").toDouble());
00662         this->setScale( rect, false);
00663     }
00664 
00665     return true;
00666 }
00667 
00668 
00669 QRectF PlotWidget::currentBoundingRect() const
00670 {
00671     QRectF rect;
00672     rect.setBottom( this->canvasMap( yLeft ).s1() );
00673     rect.setTop( this->canvasMap( yLeft ).s2() );
00674 
00675     rect.setLeft( this->canvasMap( xBottom ).s1() );
00676     rect.setRight( this->canvasMap( xBottom ).s2() );
00677 
00678     return rect;
00679 }
00680 
00681 void PlotWidget::setScale(QRectF rect, bool emit_signal)
00682 {
00683     this->setAxisScale( yLeft, rect.bottom(), rect.top());
00684     this->setAxisScale( xBottom, rect.left(), rect.right());
00685 
00686     this->updateAxes();
00687 
00688     if( emit_signal )
00689     {
00690         if( isXYPlot()) {
00691             emit undoableChange();
00692         }
00693         else{
00694             emit rectChanged(this, rect);
00695         }
00696     }
00697 }
00698 
00699 void PlotWidget::reloadPlotData()
00700 {
00701     if( isXYPlot() )
00702     {
00703         auto it = _mapped_data.numeric.find( _axisX->name() );
00704         if( it != _mapped_data.numeric.end() ){
00705             _axisX = it->second;
00706         }
00707         else{
00708             _axisX = PlotDataPtr();
00709         }
00710     }
00711 
00712     for (auto& curve_it: _curve_list)
00713     {
00714         std::shared_ptr<QwtPlotCurve>& curve_data = curve_it.second;
00715         const std::string curve_name = curve_it.first.toStdString();
00716 
00717         auto it = _mapped_data.numeric.find( curve_name );
00718         if( it != _mapped_data.numeric.end())
00719         {
00720             TimeseriesQwt* new_plotqwt = new TimeseriesQwt( it->second );
00721             new_plotqwt->setTimeOffset( _time_offset );
00722             new_plotqwt->setAlternativeAxisX( _axisX );
00723             new_plotqwt->setTransform( _current_transform );
00724             curve_data->setData( new_plotqwt );
00725         }
00726     }
00727 
00728     if( _curve_list.size() == 0){
00729         setDefaultRangeX();
00730     }
00731 }
00732 
00733 void PlotWidget::activateLegent(bool activate)
00734 {
00735     if( activate ) _legend->attach(this);
00736     else           _legend->detach();
00737 }
00738 
00739 void PlotWidget::activateGrid(bool activate)
00740 {
00741     _grid->enableX(activate);
00742     _grid->enableXMin(activate);
00743     _grid->enableY(activate);
00744     _grid->enableYMin(activate);
00745     _grid->attach(this);
00746 }
00747 
00748 void PlotWidget::configureTracker(CurveTracker::Parameter val)
00749 {
00750     _tracker->setParameter( val );
00751 }
00752 
00753 void PlotWidget::enableTracker(bool enable)
00754 {
00755     _tracker->setEnabled( enable && !isXYPlot() );
00756 }
00757 
00758 void PlotWidget::setTrackerPosition(double abs_time)
00759 {
00760     if( isXYPlot()){
00761         for (auto it: _curve_list)
00762         {
00763             QString name = it.first;
00764             TimeseriesQwt* series = static_cast<TimeseriesQwt*>( it.second->data() );
00765             auto pointXY = series->sampleFromTime(abs_time);
00766             if( pointXY ){
00767                 _point_marker[name]->setValue( pointXY.value() );
00768             }
00769         }
00770     }
00771     else{
00772         double relative_time = abs_time - _time_offset;
00773         _tracker->setPosition( QPointF( relative_time , 0.0) );
00774     }
00775 }
00776 
00777 void PlotWidget::on_changeTimeOffset(double offset)
00778 {
00779     _time_offset = offset;
00780     for (auto it: _curve_list)
00781     {
00782         TimeseriesQwt* series = static_cast<TimeseriesQwt*>( it.second->data() );
00783         series->setTimeOffset(offset);
00784     }
00785     zoomOut(false);
00786 }
00787 
00788 
00789 PlotData::RangeTime PlotWidget::getMaximumRangeX() const
00790 {
00791     double left   =  std::numeric_limits<double>::max();
00792     double right  = -std::numeric_limits<double>::max();
00793 
00794     for (auto it: _curve_list)
00795     {
00796         TimeseriesQwt* series = static_cast<TimeseriesQwt*>( it.second->data() );
00797         auto range_X = series->getVisualizationRangeX();
00798 
00799         if( !range_X ) continue;
00800 
00801         if( left  > range_X->min )    left  = range_X->min;
00802         if( right < range_X->max )    right = range_X->max;
00803     }
00804 
00805     if( left > right ){
00806         left  = 0;
00807         right = 0;
00808     }
00809 
00810     double margin = 0.1;
00811     if( fabs(right - left) > std::numeric_limits<double>::epsilon() )
00812     {
00813         margin = isXYPlot() ? ((right-left) * 0.025) : 0.0;
00814     }
00815     right = right + margin;
00816     left  = left  - margin;
00817 
00818     return PlotData::RangeTime( {left,right} );
00819 }
00820 
00821 //TODO report failure for empty dataset
00822 PlotData::RangeValue  PlotWidget::getMaximumRangeY( PlotData::RangeTime range_X, bool absolute_time) const
00823 {
00824     double top    = -std::numeric_limits<double>::max();
00825     double bottom =  std::numeric_limits<double>::max();
00826 
00827     for(auto it = _curve_list.begin(); it != _curve_list.end(); ++it)
00828     {
00829         TimeseriesQwt* series = static_cast<TimeseriesQwt*>( it->second->data() );
00830 
00831         const auto max_range_X = series->getVisualizationRangeX();
00832         if( !max_range_X ) continue;
00833 
00834         double left  = std::max(max_range_X->min, range_X.min);
00835         double right = std::min(max_range_X->max, range_X.max);
00836 
00837         if( !absolute_time )
00838         {
00839             left += _time_offset;
00840             right += _time_offset;
00841             left = std::nextafter(left, right);
00842             right = std::nextafter(right, left);
00843         }
00844 
00845         int X0 = series->data()->getIndexFromX(left);
00846         int X1 = series->data()->getIndexFromX(right);
00847 
00848         if( X0<0 || X1 <0)
00849         {
00850             qDebug() << " invalid X0/X1 range in PlotWidget::maximumRangeY";
00851             continue;
00852         }
00853         else{
00854             auto range_Y = series->getVisualizationRangeY(X0, X1);
00855             if( !range_Y )
00856             {
00857                 qDebug() << " invalid range_Y in PlotWidget::maximumRangeY";
00858                 continue;
00859             }
00860             if( top <    range_Y->max )    top    = range_Y->max;
00861             if( bottom > range_Y->min )    bottom = range_Y->min;
00862         }
00863     }
00864 
00865     double margin = 0.1;
00866 
00867     if( bottom > top ){
00868         bottom  = 0;
00869         top = 0;
00870     }
00871 
00872     if( top - bottom > std::numeric_limits<double>::epsilon() )
00873     {
00874         margin = (top-bottom) * 0.025;
00875     }
00876 
00877     const bool lower_limit = _custom_Y_limits.min > -MAX_DOUBLE;
00878     const bool upper_limit = _custom_Y_limits.max <  MAX_DOUBLE;
00879 
00880     if(lower_limit)
00881     {
00882         bottom = _custom_Y_limits.min;
00883         if( top < bottom ) top = bottom + margin;
00884     }
00885 
00886     if( upper_limit )
00887     {
00888         top = _custom_Y_limits.max;
00889         if( top < bottom ) bottom = top - margin;
00890     }
00891 
00892     if( !lower_limit && !upper_limit )
00893     {
00894         top    += margin;
00895         bottom -= margin;
00896     }
00897 
00898     return PlotData::RangeValue({ bottom,  top});
00899 }
00900 
00901 void PlotWidget::updateCurves(bool force)
00902 {
00903     for(auto it = _curve_list.begin(); it != _curve_list.end(); ++it)
00904     {
00905         TimeseriesQwt* series = static_cast<TimeseriesQwt*>( it->second->data() );
00906         series->updateData();
00907     }
00908 }
00909 
00910 
00911 void PlotWidget::replot()
00912 {
00913     if( _zoomer )
00914         _zoomer->setZoomBase( false );
00915 
00916     QwtPlot::replot();
00917 }
00918 
00919 void PlotWidget::launchRemoveCurveDialog()
00920 {
00921     RemoveCurveDialog* dialog = new RemoveCurveDialog(this);
00922     auto prev_curve_count = _curve_list.size();
00923 
00924     for(auto it = _curve_list.begin(); it != _curve_list.end(); ++it)
00925     {
00926         dialog->addCurveName( it->first );
00927     }
00928 
00929     dialog->exec();
00930 
00931     if( prev_curve_count != _curve_list.size() )
00932     {
00933         emit undoableChange();
00934     }
00935 }
00936 
00937 void PlotWidget::on_changeColorsDialog_triggered()
00938 {
00939     std::map<QString,QColor> color_by_name;
00940 
00941     for(auto it = _curve_list.begin(); it != _curve_list.end(); ++it)
00942     {
00943         const QString& curve_name = it->first;
00944         auto curve = it->second;
00945         color_by_name.insert(std::make_pair( curve_name, curve->pen().color() ));
00946     }
00947 
00948     CurveColorPick* dialog = new CurveColorPick(color_by_name, this);
00949 
00950     connect( dialog, &CurveColorPick::changeColor, this, &PlotWidget::on_changeColor,
00951              Qt::DirectConnection);
00952 
00953     dialog->exec();
00954 
00955     if( dialog->anyColorModified() )
00956     {
00957         emit undoableChange();
00958     }
00959 }
00960 
00961 void PlotWidget::on_changeColor(QString curve_name, QColor new_color)
00962 {
00963     auto it = _curve_list.find(curve_name);
00964     if( it != _curve_list.end())
00965     {
00966         auto curve = it->second;
00967         if( curve->pen().color() != new_color)
00968         {
00969             curve->setPen( new_color, 1.0 );
00970         }
00971         replot();
00972     }
00973 }
00974 
00975 void PlotWidget::on_showPoints_triggered(bool checked)
00976 {
00977     _show_line_and_points = checked;
00978     for(auto it = _curve_list.begin(); it != _curve_list.end(); ++it)
00979     {
00980         auto curve = it->second;
00981         if( _show_line_and_points )
00982         {
00983             curve->setStyle( QwtPlotCurve::LinesAndDots);
00984         }
00985         else{
00986             curve->setStyle( QwtPlotCurve::Lines);
00987         }
00988     }
00989     replot();
00990 }
00991 
00992 void PlotWidget::on_externallyResized(const QRectF& rect)
00993 {
00994     if( _current_transform != TimeseriesQwt::XYPlot)
00995     {
00996         emit rectChanged(this, rect);
00997     }
00998     else{
00999         emit undoableChange();
01000     }
01001 }
01002 
01003 
01004 void PlotWidget::zoomOut(bool emit_signal)
01005 {
01006     if( _curve_list.size() == 0)
01007     {
01008         QRectF rect(0, 1, 1, -1);
01009         this->setScale(rect, false);
01010         return;
01011     }
01012 
01013     QRectF rect;
01014     auto rangeX = getMaximumRangeX();
01015 
01016     rect.setLeft( rangeX.min );
01017     rect.setRight( rangeX.max );
01018 
01019     auto rangeY = getMaximumRangeY( rangeX, false );
01020 
01021     rect.setBottom( rangeY.min   );
01022     rect.setTop(  rangeY.max  );
01023 
01024     _magnifier->setAxisLimits( xBottom, rect.left(),   rect.right() );
01025     _magnifier->setAxisLimits( yLeft,   rect.bottom(), rect.top() );
01026 
01027     this->setScale(rect, emit_signal);
01028 }
01029 
01030 void PlotWidget::on_zoomOutHorizontal_triggered(bool emit_signal)
01031 {
01032     QRectF act = currentBoundingRect();
01033     auto rangeX = getMaximumRangeX();
01034 
01035     act.setLeft( rangeX.min );
01036     act.setRight( rangeX.max );
01037     this->setScale(act, emit_signal);
01038 }
01039 
01040 void PlotWidget::on_zoomOutVertical_triggered(bool emit_signal)
01041 {
01042     QRectF rect = currentBoundingRect();
01043     auto rangeY = getMaximumRangeY( {rect.left(), rect.right()}, false );
01044 
01045     rect.setBottom(  rangeY.min );
01046     rect.setTop(     rangeY.max );
01047 
01048     _magnifier->setAxisLimits( yLeft, rect.bottom(), rect.top() );
01049 
01050     this->setScale(rect, emit_signal);
01051 }
01052 
01053 void PlotWidget::on_noTransform_triggered(bool checked )
01054 {
01055     enableTracker(true);
01056     if(_current_transform ==  TimeseriesQwt::noTransform) return;
01057 
01058     for (auto it :_curve_list)
01059     {
01060         TimeseriesQwt* series = static_cast<TimeseriesQwt*>( it.second->data() );
01061         series->setTransform( TimeseriesQwt::noTransform );
01062         _point_marker[ it.first ]->setVisible(false);
01063     }
01064     this->setFooter("");
01065     _current_transform = ( TimeseriesQwt::noTransform );
01066 
01067     zoomOut(true);
01068     replot();
01069 }
01070 
01071 void PlotWidget::on_1stDerivativeTransform_triggered(bool checked)
01072 {
01073     enableTracker(true);
01074     if(_current_transform ==  TimeseriesQwt::firstDerivative) return;
01075 
01076     for (auto it :_curve_list)
01077     {
01078         TimeseriesQwt* series = static_cast<TimeseriesQwt*>( it.second->data() );
01079         series->setTransform( TimeseriesQwt::firstDerivative );
01080         _point_marker[ it.first ]->setVisible(false);
01081     }
01082 
01083     QFont font_title;
01084     font_title.setPointSize(10);
01085     QwtText text("1st derivative");
01086     text.setFont(font_title);
01087 
01088     this->setFooter(text);
01089     _current_transform = ( TimeseriesQwt::firstDerivative );
01090 
01091     zoomOut(true);
01092     replot();
01093 }
01094 
01095 void PlotWidget::on_2ndDerivativeTransform_triggered(bool checked)
01096 {
01097     enableTracker(true);
01098     if(_current_transform ==  TimeseriesQwt::secondDerivative) return;
01099 
01100     for (auto it :_curve_list)
01101     {
01102         TimeseriesQwt* series = static_cast<TimeseriesQwt*>( it.second->data() );
01103         series->setTransform( TimeseriesQwt::secondDerivative );
01104         _point_marker[ it.first ]->setVisible(false);
01105     }
01106 
01107     QFont font_title;
01108     font_title.setPointSize(10);
01109     QwtText text("2nd derivative");
01110     text.setFont(font_title);
01111 
01112     this->setFooter(text);
01113     _current_transform = ( TimeseriesQwt::secondDerivative );
01114 
01115     zoomOut(true);
01116     replot();
01117 }
01118 
01119 bool PlotWidget::isXYPlot() const
01120 {
01121     return (_current_transform == TimeseriesQwt::XYPlot && _axisX);
01122 }
01123 
01124 
01125 void PlotWidget::on_convertToXY_triggered(bool)
01126 {
01127     enableTracker(false);
01128 
01129     if( !_axisX )
01130     {
01131         QMessageBox::warning(0, tr("Warning"),
01132                              tr("To show a XY plot, you must first provide an alternative X axis.\n"
01133                                 "You can do this drag'n dropping a curve using the RIGHT mouse button "
01134                                 "instead of the left mouse button.") );
01135         return;
01136     }
01137 
01138     _current_transform = TimeseriesQwt::XYPlot;
01139 
01140     for (auto it :_curve_list)
01141     {
01142         TimeseriesQwt* series = static_cast<TimeseriesQwt*>( it.second->data() );
01143         series->setAlternativeAxisX( _axisX );
01144         series->setTransform( TimeseriesQwt::XYPlot );
01145         _point_marker[ it.first ]->setVisible(true);
01146     }
01147 
01148     QFont font_footer;
01149     font_footer.setPointSize(10);
01150     QwtText text( _axisX->name().c_str() );
01151     text.setFont(font_footer);
01152 
01153     this->setFooter( text );
01154 
01155     zoomOut(true);
01156     replot();
01157 }
01158 
01159 void PlotWidget::changeAxisX(QString curve_name)
01160 {
01161     auto it = _mapped_data.numeric.find( curve_name.toStdString() );
01162     if( it != _mapped_data.numeric.end())
01163     {
01164         _axisX = it->second;
01165         _action_phaseXY->trigger();
01166     }
01167     else{
01168         // do nothing (?)
01169     }
01170 }
01171 
01172 void PlotWidget::on_savePlotToFile()
01173 {
01174     QString fileName;
01175 
01176     QFileDialog saveDialog;
01177     saveDialog.setAcceptMode(QFileDialog::AcceptSave);
01178     saveDialog.setDefaultSuffix("png");
01179 
01180 #ifndef QWT_NO_SVG
01181     saveDialog.setNameFilter("Compatible formats (*.jpg *.jpeg *.svg *.png)");
01182 #else
01183     saveDialog.setNameFilter("Compatible formats (*.jpg *.jpeg *.png)");
01184 #endif
01185     saveDialog.exec();
01186 
01187     if(saveDialog.result() == QDialog::Accepted && !saveDialog.selectedFiles().empty())
01188     {
01189         fileName = saveDialog.selectedFiles().first();
01190 
01191         QPixmap pixmap (1200,900);
01192         QPainter * painter = new QPainter(&pixmap);
01193 
01194         if ( !fileName.isEmpty() )
01195         {
01196             QwtPlotRenderer rend;
01197             rend.render(this, painter, QRect(0, 0, pixmap.width(), pixmap.height()));
01198             pixmap.save(fileName);
01199         }
01200     }
01201 }
01202 
01203 void PlotWidget::on_editAxisLimits_triggered()
01204 {
01205     auto rangeX = this->getMaximumRangeX();
01206 
01207     //temporary reset the limit during editing
01208     _custom_Y_limits.min = -MAX_DOUBLE;
01209     _custom_Y_limits.max =  MAX_DOUBLE;
01210 
01211     auto rangeY = getMaximumRangeY(rangeX, false);
01212 
01213     _axis_limits_dialog->setDefaultRange(rangeY);
01214     _axis_limits_dialog->exec();
01215 
01216     _custom_Y_limits = _axis_limits_dialog->rangeY();
01217 
01218     on_zoomOutVertical_triggered(false);
01219     replot();
01220     emit undoableChange();
01221 }
01222 
01223 
01224 bool PlotWidget::eventFilter(QObject *obj, QEvent *event)
01225 {
01226     switch( event->type() )
01227     {
01228 
01229     case QEvent::MouseButtonPress:
01230     {
01231         QMouseEvent *mouse_event = static_cast<QMouseEvent*>(event);
01232 
01233         if( mouse_event->button() == Qt::LeftButton )
01234         {
01235             if( mouse_event->modifiers() == Qt::ShiftModifier) // time tracker
01236             {
01237                 const QPoint point = mouse_event->pos();
01238                 QPointF pointF ( invTransform( xBottom, point.x()),
01239                                  invTransform( yLeft, point.y()) );
01240                 emit trackerMoved(pointF);
01241             }
01242             if( mouse_event->modifiers() == Qt::ControlModifier) // panner
01243             {
01244                 QApplication::setOverrideCursor(QCursor(QPixmap(":/icons/resources/move.png")));
01245             }
01246             else if(mouse_event->modifiers() == Qt::NoModifier )
01247             {
01248                 // delegate to _zoomer
01249             }
01250         }
01251         else if( mouse_event->button() == Qt::RightButton )
01252         {
01253             if( mouse_event->modifiers() == Qt::NoModifier) // show menu
01254             {
01255                 canvasContextMenuTriggered( mouse_event->pos() );
01256             }
01257             else if( mouse_event->modifiers() == Qt::ControlModifier) // Start swapping two plots
01258             {
01259 
01260                 QDrag *drag = new QDrag(this);
01261                 QMimeData *mimeData = new QMimeData;
01262 
01263                 QByteArray data;
01264                 QDataStream dataStream(&data, QIODevice::WriteOnly);
01265 
01266                 dataStream << this->windowTitle();
01267 
01268                 mimeData->setData("plot_area", data );
01269                 drag->setMimeData(mimeData);
01270                 drag->exec();
01271             }
01272         }
01273     }break;
01274     //---------------------------------
01275     case QEvent::MouseMove:
01276     {
01277         QMouseEvent *mouse_event = static_cast<QMouseEvent*>(event);
01278 
01279         if ( mouse_event->buttons() == Qt::LeftButton &&
01280              mouse_event->modifiers() == Qt::ShiftModifier )
01281         {
01282             const QPoint point = mouse_event->pos();
01283             QPointF pointF ( invTransform( xBottom, point.x()),
01284                              invTransform( yLeft, point.y()) );
01285             emit trackerMoved(pointF);
01286         }
01287     }break;
01288     //---------------------------------
01289     case QEvent::MouseButtonRelease :
01290     {
01291         QApplication::restoreOverrideCursor();
01292     }break;
01293     //---------------------------------
01294     case QEvent::KeyPress:
01295     {
01296 //        QKeyEvent *key_event = static_cast<QKeyEvent*>(event);
01297 //        qDebug() << key_event->key();
01298      }break;
01299     //---------------------------------
01300     case QEvent::Paint :
01301     {
01302         if ( obj == this->canvas())
01303         {
01304             if ( !_fps_timeStamp.isValid() )
01305             {
01306                 _fps_timeStamp.start();
01307                 _fps_counter = 0;
01308             }
01309             else{
01310                 _fps_counter++;
01311 
01312                 const double elapsed = _fps_timeStamp.elapsed() / 1000.0;
01313                 if ( elapsed >= 1 )
01314                 {
01315                     QFont font_title;
01316                     font_title.setPointSize(9);
01317                     QwtText fps;
01318                     fps.setText( QString::number( qRound( _fps_counter / elapsed ) ) );
01319                     fps.setFont(font_title);
01320                     //qDebug() << _fps_counter / elapsed ;
01321                     _fps_counter = 0;
01322                     _fps_timeStamp.start();
01323                 }
01324             }
01325         }
01326     }break;
01327 
01328     } //end switch
01329 
01330     return QwtPlot::eventFilter( obj, event );
01331 }
01332 


plotjuggler
Author(s): Davide Faconti
autogenerated on Fri Sep 1 2017 02:41:56