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
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
00645
00646
00647
00648
00649
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
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
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
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)
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)
01243 {
01244 QApplication::setOverrideCursor(QCursor(QPixmap(":/icons/resources/move.png")));
01245 }
01246 else if(mouse_event->modifiers() == Qt::NoModifier )
01247 {
01248
01249 }
01250 }
01251 else if( mouse_event->button() == Qt::RightButton )
01252 {
01253 if( mouse_event->modifiers() == Qt::NoModifier)
01254 {
01255 canvasContextMenuTriggered( mouse_event->pos() );
01256 }
01257 else if( mouse_event->modifiers() == Qt::ControlModifier)
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
01297
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
01321 _fps_counter = 0;
01322 _fps_timeStamp.start();
01323 }
01324 }
01325 }
01326 }break;
01327
01328 }
01329
01330 return QwtPlot::eventFilter( obj, event );
01331 }
01332