plotwidget.cpp
Go to the documentation of this file.
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     // disable right button. keep mouse wheel
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         // Without the X axis, transform all the curves to noTransform
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     // prevent spurious exits
00532     if( canvas()->rect().contains( local_pos ))
00533     {
00534         // changeBackgroundColor( QColor( 250, 150, 150 ) );
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             // height is negative!!!!
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             // height must be negative!!!!
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    // qDebug() << canvasBoundingRect();
00934 }
00935 
00936 void PlotWidget::setConstantRatioXY(bool active)
00937 {
00938     _keep_aspect_ratio = active;
00939     if( isXYPlot() && active)
00940     {
00941         // TODo rescaler
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 //TODO report failure for empty dataset
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         //TODO check res and do something if false.
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         //TODO: do nothing (?)
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     //temporary reset the limit during editing
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; // don't pass to canvas().
01648             }
01649         }
01650 
01651         return false;
01652     }
01653 
01654     case QEvent::MouseButtonPress:
01655     {
01656         if( _dragging.mode != DragInfo::NONE)
01657         {
01658             return true; // don't pass to canvas().
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) // time tracker
01667             {
01668                 QPointF pointF ( invTransform( xBottom, press_point.x()),
01669                                  invTransform( yLeft, press_point.y()) );
01670                 emit trackerMoved(pointF);
01671                 return true; // don't pass to canvas().
01672             }
01673             else if( mouse_event->modifiers() == Qt::ControlModifier) // panner
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; // send to canvas()
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) // show menu
01705             {
01706                 canvasContextMenuTriggered( mouse_event->pos() );
01707                 return true; // don't pass to canvas().
01708             }
01709             else if( mouse_event->modifiers() == Qt::ControlModifier) // Start swapping two plots
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; // don't pass to canvas().
01725             }
01726         }
01727     }break;
01728         //---------------------------------
01729     case QEvent::MouseMove:
01730     {
01731         if( _dragging.mode != DragInfo::NONE)
01732         {
01733             return true; // don't pass to canvas().
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         // If you think that this code doesn't make sense, you are right.
01771         // This is the workaround I have eventually found to avoid the problem with spurious
01772         // QEvent::DragLeave (I have never found the origin of the bug).
01773         dropEvent(nullptr);
01774         return true;
01775     }break;
01776 
01777     default: {}
01778 
01779     } //end switch
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                 //                QAbstractButton* buttonDontRepear = msgBox.addButton("Don't show again",
01835                 //                                                                     QMessageBox::ActionRole);
01836                 msgBox.addButton("Continue", QMessageBox::AcceptRole);
01837                 msgBox.exec();
01838 
01839                 //                if (msgBox.clickedButton() == buttonDontRepear)
01840                 //                {
01841                 //                    if_xy_plot_failed_show_dialog = false;
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   //  qDebug() << replot_count++;
01914 }
01915 


plotjuggler
Author(s): Davide Faconti
autogenerated on Wed Jul 3 2019 19:28:04