00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 #include "KDChartLeveyJenningsDiagram.h"
00024 #include "KDChartLeveyJenningsDiagram_p.h"
00025
00026 #include <QDateTime>
00027 #include <QFontMetrics>
00028 #include <QPainter>
00029 #include <QSvgRenderer>
00030 #include <QVector>
00031
00032 #include "KDChartChart.h"
00033 #include "KDChartTextAttributes.h"
00034 #include "KDChartAbstractGrid.h"
00035
00036 #include <KDABLibFakes>
00037
00038 using namespace KDChart;
00039 using namespace std;
00040
00041 LeveyJenningsDiagram::Private::Private()
00042 {
00043 }
00044
00045 LeveyJenningsDiagram::Private::~Private() {}
00046
00047
00048 #define d d_func()
00049
00050
00051 LeveyJenningsDiagram::LeveyJenningsDiagram( QWidget* parent, LeveyJenningsCoordinatePlane* plane )
00052 : LineDiagram( new Private(), parent, plane )
00053 {
00054 init();
00055 }
00056
00057 void LeveyJenningsDiagram::init()
00058 {
00059 d->lotChangedPosition = Qt::AlignTop;
00060 d->fluidicsPackChangedPosition = Qt::AlignBottom;
00061 d->sensorChangedPosition = Qt::AlignBottom;
00062
00063 d->scanLinePen = QPen( Qt::blue );
00064 setPen( d->scanLinePen );
00065
00066 d->expectedMeanValue = 0.0;
00067 d->expectedStandardDeviation = 0.0;
00068
00069 d->diagram = this;
00070
00071 d->icons[ LotChanged ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/karo_black.svg" );
00072 d->icons[ SensorChanged ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/karo_red.svg" );
00073 d->icons[ FluidicsPackChanged ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/karo_blue.svg" );
00074 d->icons[ OkDataPoint ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/circle_blue.svg" );
00075 d->icons[ NotOkDataPoint ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/circle_blue_red.svg" );
00076
00077 setSelectionMode( QAbstractItemView::SingleSelection );
00078 }
00079
00080 LeveyJenningsDiagram::~LeveyJenningsDiagram()
00081 {
00082 }
00083
00087 LineDiagram * LeveyJenningsDiagram::clone() const
00088 {
00089 LeveyJenningsDiagram* newDiagram = new LeveyJenningsDiagram( new Private( *d ) );
00090 return newDiagram;
00091 }
00092
00093 bool LeveyJenningsDiagram::compare( const LeveyJenningsDiagram* other )const
00094 {
00095 if( other == this ) return true;
00096 if( ! other ){
00097 return false;
00098 }
00099
00100
00101
00102
00103
00104 return
00105 ( static_cast<const LineDiagram*>(this)->compare( other ) );
00106 }
00107
00112 void LeveyJenningsDiagram::setLotChangedSymbolPosition( Qt::Alignment pos )
00113 {
00114 if( d->lotChangedPosition == pos )
00115 return;
00116
00117 d->lotChangedPosition = pos;
00118 update();
00119 }
00120
00124 Qt::Alignment LeveyJenningsDiagram::lotChangedSymbolPosition() const
00125 {
00126 return d->lotChangedPosition;
00127 }
00128
00133 void LeveyJenningsDiagram::setFluidicsPackChangedSymbolPosition( Qt::Alignment pos )
00134 {
00135 if( d->fluidicsPackChangedPosition == pos )
00136 return;
00137
00138 d->fluidicsPackChangedPosition = pos;
00139 update();
00140 }
00141
00145 Qt::Alignment LeveyJenningsDiagram::fluidicsPackChangedSymbolPosition() const
00146 {
00147 return d->fluidicsPackChangedPosition;
00148 }
00149
00154 void LeveyJenningsDiagram::setSensorChangedSymbolPosition( Qt::Alignment pos )
00155 {
00156 if( d->sensorChangedPosition == pos )
00157 return;
00158
00159 d->sensorChangedPosition = pos;
00160 update();
00161 }
00162
00166 Qt::Alignment LeveyJenningsDiagram::sensorChangedSymbolPosition() const
00167 {
00168 return d->sensorChangedPosition;
00169 }
00170
00174 void LeveyJenningsDiagram::setFluidicsPackChanges( const QVector< QDateTime >& changes )
00175 {
00176 if( d->fluidicsPackChanges == changes )
00177 return;
00178
00179 d->fluidicsPackChanges = changes;
00180 update();
00181 }
00182
00186 QVector< QDateTime > LeveyJenningsDiagram::fluidicsPackChanges() const
00187 {
00188 return d->fluidicsPackChanges;
00189 }
00190
00194 void LeveyJenningsDiagram::setSensorChanges( const QVector< QDateTime >& changes )
00195 {
00196 if( d->sensorChanges == changes )
00197 return;
00198
00199 d->sensorChanges = changes;
00200 update();
00201 }
00202
00206 void LeveyJenningsDiagram::setScanLinePen( const QPen& pen )
00207 {
00208 if( d->scanLinePen == pen )
00209 return;
00210
00211 d->scanLinePen = pen;
00212 update();
00213 }
00214
00218 QPen LeveyJenningsDiagram::scanLinePen() const
00219 {
00220 return d->scanLinePen;
00221 }
00222
00226 QString LeveyJenningsDiagram::symbol( Symbol symbol ) const
00227 {
00228 return d->icons[ symbol ];
00229 }
00230
00234 void LeveyJenningsDiagram::setSymbol( Symbol symbol, const QString& filename )
00235 {
00236 if( d->icons[ symbol ] == filename )
00237 return;
00238
00239 delete d->iconRenderer[ symbol ];
00240 d->iconRenderer[ symbol ] = 0;
00241
00242 d->icons[ symbol ] = filename;
00243
00244 update();
00245 }
00246
00250 QVector< QDateTime > LeveyJenningsDiagram::sensorChanges() const
00251 {
00252 return d->sensorChanges;
00253 }
00254
00258 void LeveyJenningsDiagram::setExpectedMeanValue( float meanValue )
00259 {
00260 if( d->expectedMeanValue == meanValue )
00261 return;
00262
00263 d->expectedMeanValue = meanValue;
00264 d->setYAxisRange();
00265 update();
00266 }
00267
00271 float LeveyJenningsDiagram::expectedMeanValue() const
00272 {
00273 return d->expectedMeanValue;
00274 }
00275
00279 void LeveyJenningsDiagram::setExpectedStandardDeviation( float sd )
00280 {
00281 if( d->expectedStandardDeviation == sd )
00282 return;
00283
00284 d->expectedStandardDeviation = sd;
00285 d->setYAxisRange();
00286 update();
00287 }
00288
00292 float LeveyJenningsDiagram::expectedStandardDeviation() const
00293 {
00294 return d->expectedStandardDeviation;
00295 }
00296
00300 float LeveyJenningsDiagram::calculatedMeanValue() const
00301 {
00302 return d->calculatedMeanValue;
00303 }
00304
00308 float LeveyJenningsDiagram::calculatedStandardDeviation() const
00309 {
00310 return d->calculatedStandardDeviation;
00311 }
00312
00313 void LeveyJenningsDiagram::setModel( QAbstractItemModel* model )
00314 {
00315 if( this->model() != 0 )
00316 {
00317 disconnect( this->model(), SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ),
00318 this, SLOT( calculateMeanAndStandardDeviation() ) );
00319 disconnect( this->model(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ),
00320 this, SLOT( calculateMeanAndStandardDeviation() ) );
00321 disconnect( this->model(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ),
00322 this, SLOT( calculateMeanAndStandardDeviation() ) );
00323 disconnect( this->model(), SIGNAL( columnsInserted( const QModelIndex&, int, int ) ),
00324 this, SLOT( calculateMeanAndStandardDeviation() ) );
00325 disconnect( this->model(), SIGNAL( columnsRemoved( const QModelIndex&, int, int ) ),
00326 this, SLOT( calculateMeanAndStandardDeviation() ) );
00327 disconnect( this->model(), SIGNAL( modelReset() ),
00328 this, SLOT( calculateMeanAndStandardDeviation() ) );
00329 disconnect( this->model(), SIGNAL( layoutChanged() ),
00330 this, SLOT( calculateMeanAndStandardDeviation() ) );
00331 }
00332 LineDiagram::setModel( model );
00333 if( this->model() != 0 )
00334 {
00335 connect( this->model(), SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ),
00336 this, SLOT( calculateMeanAndStandardDeviation() ) );
00337 connect( this->model(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ),
00338 this, SLOT( calculateMeanAndStandardDeviation() ) );
00339 connect( this->model(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ),
00340 this, SLOT( calculateMeanAndStandardDeviation() ) );
00341 connect( this->model(), SIGNAL( columnsInserted( const QModelIndex&, int, int ) ),
00342 this, SLOT( calculateMeanAndStandardDeviation() ) );
00343 connect( this->model(), SIGNAL( columnsRemoved( const QModelIndex&, int, int ) ),
00344 this, SLOT( calculateMeanAndStandardDeviation() ) );
00345 connect( this->model(), SIGNAL( modelReset() ),
00346 this, SLOT( calculateMeanAndStandardDeviation() ) );
00347 connect( this->model(), SIGNAL( layoutChanged() ),
00348 this, SLOT( calculateMeanAndStandardDeviation() ) );
00349
00350 calculateMeanAndStandardDeviation();
00351 }
00352 }
00353
00354
00355
00356 void LeveyJenningsDiagram::calculateMeanAndStandardDeviation() const
00357 {
00358 QVector< double > values;
00359
00360 const QAbstractItemModel& m = *model();
00361 const int rowCount = m.rowCount( rootIndex() );
00362
00363 for( int row = 0; row < rowCount; ++row )
00364 {
00365 const QVariant var = m.data( m.index( row, 1, rootIndex() ) );
00366 if( !var.isValid() )
00367 continue;
00368 const double value = var.toDouble();
00369 if( ISNAN( value ) )
00370 continue;
00371 values << value;
00372 }
00373
00374 double sum = 0.0;
00375 double sumSquares = 0.0;
00376 KDAB_FOREACH( double value, values )
00377 {
00378 sum += value;
00379 sumSquares += value * value;
00380 }
00381
00382 const int N = values.count();
00383
00384 d->calculatedMeanValue = sum / N;
00385 d->calculatedStandardDeviation = sqrt( ( static_cast< double >( N ) * sumSquares - sum * sum ) / ( N * ( N - 1 ) ) );
00386 }
00387
00388
00389 static QDate floorDay( const QDateTime& dt )
00390 {
00391 return dt.date();
00392 }
00393
00394
00395 static QDate ceilDay( const QDateTime& dt )
00396 {
00397 QDate result = dt.date();
00398
00399 if( QDateTime( result, QTime() ) < dt )
00400 result = result.addDays( 1 );
00401
00402 return result;
00403 }
00404
00405
00406 static QDateTime floorHour( const QDateTime& dt )
00407 {
00408 return QDateTime( dt.date(), QTime( dt.time().hour(), 0 ) );
00409 }
00410
00411
00412 static QDateTime ceilHour( const QDateTime& dt )
00413 {
00414 QDateTime result( dt.date(), QTime( dt.time().hour(), 0 ) );
00415
00416 if( result < dt )
00417 result = result.addSecs( 3600 );
00418
00419 return result;
00420 }
00421
00423 const QPair<QPointF, QPointF> LeveyJenningsDiagram::calculateDataBoundaries() const
00424 {
00425 const double yMin = d->expectedMeanValue - 4 * d->expectedStandardDeviation;
00426 const double yMax = d->expectedMeanValue + 4 * d->expectedStandardDeviation;
00427
00428 d->setYAxisRange();
00429
00430
00431 const QPair< QDateTime, QDateTime > range = timeRange();
00432 const unsigned int minTime = range.first.toTime_t();
00433 const unsigned int maxTime = range.second.toTime_t();
00434
00435 const double xMin = minTime / static_cast< double >( 24 * 60 * 60 );
00436 const double xMax = maxTime / static_cast< double >( 24 * 60 * 60 ) - xMin;
00437
00438 const QPointF bottomLeft( QPointF( 0, yMin ) );
00439 const QPointF topRight( QPointF( xMax, yMax ) );
00440
00441 return QPair< QPointF, QPointF >( bottomLeft, topRight );
00442 }
00443
00447 QPair< QDateTime, QDateTime > LeveyJenningsDiagram::timeRange() const
00448 {
00449 if( d->timeRange != QPair< QDateTime, QDateTime >() )
00450 return d->timeRange;
00451
00452 const QAbstractItemModel& m = *model();
00453 const int rowCount = m.rowCount( rootIndex() );
00454
00455 const QDateTime begin = m.data( m.index( 0, 3, rootIndex() ) ).toDateTime();
00456 const QDateTime end = m.data( m.index( rowCount - 1, 3, rootIndex() ) ).toDateTime();
00457
00458 if( begin.secsTo( end ) > 86400 )
00459 {
00460
00461
00462 const QDate min = floorDay( begin );
00463 const QDate max = ceilDay( end );
00464 return QPair< QDateTime, QDateTime >( QDateTime( min ), QDateTime( max ) );
00465 }
00466 else if( begin.secsTo( end ) > 3600 )
00467 {
00468
00469
00470 const QDateTime min = floorHour( begin );
00471 const QDateTime max = ceilHour( end );
00472 return QPair< QDateTime, QDateTime >( min, max );
00473 }
00474 return QPair< QDateTime, QDateTime >( begin, end );
00475 }
00476
00481 void LeveyJenningsDiagram::setTimeRange( const QPair< QDateTime, QDateTime >& timeRange )
00482 {
00483 if( d->timeRange == timeRange )
00484 return;
00485
00486 d->timeRange = timeRange;
00487 update();
00488 }
00489
00493 void LeveyJenningsDiagram::drawChanges( PaintContext* ctx )
00494 {
00495 const unsigned int minTime = timeRange().first.toTime_t();
00496
00497 KDAB_FOREACH( const QDateTime& dt, d->fluidicsPackChanges )
00498 {
00499 const double xValue = ( dt.toTime_t() - minTime ) / static_cast< double >( 24 * 60 * 60 );
00500 const QPointF point( xValue, 0.0 );
00501 drawFluidicsPackChangedSymbol( ctx, point );
00502 }
00503
00504 KDAB_FOREACH( const QDateTime& dt, d->sensorChanges )
00505 {
00506 const double xValue = ( dt.toTime_t() - minTime ) / static_cast< double >( 24 * 60 * 60 );
00507 const QPointF point( xValue, 0.0 );
00508 drawSensorChangedSymbol( ctx, point );
00509 }
00510 }
00511
00513 void LeveyJenningsDiagram::paint( PaintContext* ctx )
00514 {
00515 d->reverseMapper.clear();
00516
00517
00518
00519 if ( !checkInvariants( true ) ) return;
00520 if ( !AbstractGrid::isBoundariesValid(dataBoundaries()) ) return;
00521
00522 QPainter* const painter = ctx->painter();
00523 const PainterSaver p( painter );
00524 if( model()->rowCount( rootIndex() ) == 0 || model()->columnCount( rootIndex() ) < 4 )
00525 return;
00526
00527 AbstractCoordinatePlane* const plane = ctx->coordinatePlane();
00528 ctx->setCoordinatePlane( plane->sharedAxisMasterPlane( painter ) );
00529
00530 const QAbstractItemModel& m = *model();
00531 const int rowCount = m.rowCount( rootIndex() );
00532
00533 const unsigned int minTime = timeRange().first.toTime_t();
00534
00535 painter->setRenderHint( QPainter::Antialiasing, true );
00536
00537 int prevLot = -1;
00538 QPointF prevPoint;
00539 bool hadMissingValue = false;
00540
00541 for( int row = 0; row < rowCount; ++row )
00542 {
00543 const QModelIndex lotIndex = m.index( row, 0, rootIndex() );
00544 const QModelIndex valueIndex = m.index( row, 1, rootIndex() );
00545 const QModelIndex okIndex = m.index( row, 2, rootIndex() );
00546 const QModelIndex timeIndex = m.index( row, 3, rootIndex() );
00547 const QModelIndex expectedMeanIndex = m.index( row, 4, rootIndex() );
00548 const QModelIndex expectedSDIndex = m.index( row, 5, rootIndex() );
00549
00550 painter->setPen( pen( lotIndex ) );
00551
00552 QVariant vValue = m.data( valueIndex );
00553 double value = vValue.toDouble();
00554 const int lot = m.data( lotIndex ).toInt();
00555 const bool ok = m.data( okIndex ).toBool();
00556 const QDateTime time = m.data( timeIndex ).toDateTime();
00557 const double xValue = ( time.toTime_t() - minTime ) / static_cast< double >( 24 * 60 * 60 );
00558
00559 QVariant vExpectedMean = m.data( expectedMeanIndex );
00560 const double expectedMean = vExpectedMean.toDouble();
00561 QVariant vExpectedSD = m.data( expectedSDIndex );
00562 const double expectedSD = vExpectedSD.toDouble();
00563
00564 QPointF point = ctx->coordinatePlane()->translate( QPointF( xValue, value ) );
00565
00566 if( vValue.isNull() )
00567 {
00568 hadMissingValue = true;
00569 }
00570 else
00571 {
00572 if( !vExpectedMean.isNull() && !vExpectedSD.isNull() )
00573 {
00574
00575 value -= expectedMean;
00576 value /= expectedSD;
00577 value *= d->expectedStandardDeviation;
00578 value += d->expectedMeanValue;
00579 point = ctx->coordinatePlane()->translate( QPointF( xValue, value ) );
00580 }
00581
00582 if( prevLot == lot )
00583 {
00584 const QPen pen = painter->pen();
00585 QPen newPen = pen;
00586
00587 if( hadMissingValue )
00588 {
00589 newPen.setDashPattern( QVector< qreal >() << 4.0 << 4.0 );
00590 }
00591
00592 painter->setPen( newPen );
00593 painter->drawLine( prevPoint, point );
00594 painter->setPen( pen );
00595
00596 }
00597 else if( row > 0 )
00598 {
00599 drawLotChangeSymbol( ctx, QPointF( xValue, value ) );
00600 }
00601
00602 if( value <= d->expectedMeanValue + 4 * d->expectedStandardDeviation &&
00603 value >= d->expectedMeanValue - 4 * d->expectedStandardDeviation )
00604 {
00605 const QPointF location( xValue, value );
00606 drawDataPointSymbol( ctx, location, ok );
00607 d->reverseMapper.addCircle( valueIndex.row(),
00608 valueIndex.column(),
00609 ctx->coordinatePlane()->translate( location ),
00610 iconRect().size() );
00611 }
00612 prevLot = lot;
00613 prevPoint = point;
00614 hadMissingValue = false;
00615 }
00616
00617 const QModelIndex current = selectionModel()->currentIndex();
00618 if( selectionModel()->rowIntersectsSelection( lotIndex.row(), lotIndex.parent() ) || current.sibling( current.row(), 0 ) == lotIndex )
00619 {
00620 const QPen pen = ctx->painter()->pen();
00621 painter->setPen( d->scanLinePen );
00622 painter->drawLine( ctx->coordinatePlane()->translate( QPointF( xValue, d->expectedMeanValue - 4 *
00623 d->expectedStandardDeviation ) ),
00624 ctx->coordinatePlane()->translate( QPointF( xValue, d->expectedMeanValue + 4 *
00625 d->expectedStandardDeviation ) ) );
00626 painter->setPen( pen );
00627 }
00628 }
00629
00630 drawChanges( ctx );
00631
00632 ctx->setCoordinatePlane( plane );
00633 }
00634
00640 void LeveyJenningsDiagram::drawDataPointSymbol( PaintContext* ctx, const QPointF& pos, bool ok )
00641 {
00642 const Symbol type = ok ? OkDataPoint : NotOkDataPoint;
00643
00644 QPainter* const painter = ctx->painter();
00645 const PainterSaver ps( painter );
00646 const QPointF transPos = ctx->coordinatePlane()->translate( pos ).toPoint();
00647 painter->translate( transPos );
00648
00649 painter->setClipping( false );
00650 iconRenderer( type )->render( painter, iconRect() );
00651 }
00652
00658 void LeveyJenningsDiagram::drawLotChangeSymbol( PaintContext* ctx, const QPointF& pos )
00659 {
00660 const QPointF transPos = ctx->coordinatePlane()->translate(
00661 QPointF( pos.x(), d->lotChangedPosition & Qt::AlignTop ? d->expectedMeanValue +
00662 4 * d->expectedStandardDeviation
00663 : d->expectedMeanValue -
00664 4 * d->expectedStandardDeviation ) );
00665
00666
00667 QPainter* const painter = ctx->painter();
00668 const PainterSaver ps( painter );
00669 painter->setClipping( false );
00670 painter->translate( transPos );
00671 iconRenderer( LotChanged )->render( painter, iconRect() );
00672 }
00673
00679 void LeveyJenningsDiagram::drawSensorChangedSymbol( PaintContext* ctx, const QPointF& pos )
00680 {
00681 const QPointF transPos = ctx->coordinatePlane()->translate(
00682 QPointF( pos.x(), d->sensorChangedPosition & Qt::AlignTop ? d->expectedMeanValue +
00683 4 * d->expectedStandardDeviation
00684 : d->expectedMeanValue -
00685 4 * d->expectedStandardDeviation ) );
00686
00687 QPainter* const painter = ctx->painter();
00688 const PainterSaver ps( painter );
00689 painter->setClipping( false );
00690 painter->translate( transPos );
00691 iconRenderer( SensorChanged )->render( painter, iconRect() );
00692 }
00693
00699 void LeveyJenningsDiagram::drawFluidicsPackChangedSymbol( PaintContext* ctx, const QPointF& pos )
00700 {
00701 const QPointF transPos = ctx->coordinatePlane()->translate(
00702 QPointF( pos.x(), d->fluidicsPackChangedPosition & Qt::AlignTop ? d->expectedMeanValue +
00703 4 * d->expectedStandardDeviation
00704 : d->expectedMeanValue -
00705 4 * d->expectedStandardDeviation ) );
00706
00707 QPainter* const painter = ctx->painter();
00708 const PainterSaver ps( painter );
00709 painter->setClipping( false );
00710 painter->translate( transPos );
00711 iconRenderer( FluidicsPackChanged )->render( painter, iconRect() );
00712 }
00713
00717 QRectF LeveyJenningsDiagram::iconRect() const
00718 {
00719 const Measure m( 12.5, KDChartEnums::MeasureCalculationModeAuto, KDChartEnums::MeasureOrientationAuto );
00720 TextAttributes test;
00721 test.setFontSize( m );
00722 const QFontMetrics fm( test.calculatedFont( coordinatePlane()->parent(), KDChartEnums::MeasureOrientationAuto ) );
00723 const qreal height = fm.height() / 1.2;
00724 return QRectF( -height / 2.0, -height / 2.0, height, height );
00725 }
00726
00730 QSvgRenderer* LeveyJenningsDiagram::iconRenderer( Symbol symbol )
00731 {
00732 if( d->iconRenderer[ symbol ] == 0 )
00733 d->iconRenderer[ symbol ] = new QSvgRenderer( d->icons[ symbol ], this );
00734
00735 return d->iconRenderer[ symbol ];
00736 }