00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 #include "KDChartLeveyJenningsDiagram.h"
00027 #include "KDChartLeveyJenningsDiagram_p.h"
00028
00029 #include <QDateTime>
00030 #include <QFontMetrics>
00031 #include <QPainter>
00032 #include <QSvgRenderer>
00033 #include <QVector>
00034
00035 #include "KDChartChart.h"
00036 #include "KDChartTextAttributes.h"
00037 #include "KDChartAbstractGrid.h"
00038
00039 #include <KDABLibFakes>
00040
00041 using namespace KDChart;
00042 using namespace std;
00043
00044 LeveyJenningsDiagram::Private::Private()
00045 {
00046 }
00047
00048 LeveyJenningsDiagram::Private::~Private() {}
00049
00050
00051 #define d d_func()
00052
00053
00054 LeveyJenningsDiagram::LeveyJenningsDiagram( QWidget* parent, LeveyJenningsCoordinatePlane* plane )
00055 : LineDiagram( new Private(), parent, plane )
00056 {
00057 init();
00058 }
00059
00060 void LeveyJenningsDiagram::init()
00061 {
00062 d->lotChangedPosition = Qt::AlignTop;
00063 d->fluidicsPackChangedPosition = Qt::AlignBottom;
00064 d->sensorChangedPosition = Qt::AlignBottom;
00065
00066 d->scanLinePen = QPen( Qt::blue );
00067 setPen( d->scanLinePen );
00068
00069 d->expectedMeanValue = 0.0;
00070 d->expectedStandardDeviation = 0.0;
00071
00072 d->diagram = this;
00073
00074 d->icons[ LotChanged ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/karo_black.svg" );
00075 d->icons[ SensorChanged ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/karo_red.svg" );
00076 d->icons[ FluidicsPackChanged ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/karo_blue.svg" );
00077 d->icons[ OkDataPoint ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/circle_blue.svg" );
00078 d->icons[ NotOkDataPoint ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/circle_blue_red.svg" );
00079
00080 setSelectionMode( QAbstractItemView::SingleSelection );
00081 }
00082
00083 LeveyJenningsDiagram::~LeveyJenningsDiagram()
00084 {
00085 }
00086
00090 LineDiagram * LeveyJenningsDiagram::clone() const
00091 {
00092 LeveyJenningsDiagram* newDiagram = new LeveyJenningsDiagram( new Private( *d ) );
00093 return newDiagram;
00094 }
00095
00096 bool LeveyJenningsDiagram::compare( const LeveyJenningsDiagram* other )const
00097 {
00098 if( other == this ) return true;
00099 if( ! other ){
00100 return false;
00101 }
00102
00103
00104
00105
00106
00107 return
00108 ( static_cast<const LineDiagram*>(this)->compare( other ) );
00109 }
00110
00115 void LeveyJenningsDiagram::setLotChangedSymbolPosition( Qt::Alignment pos )
00116 {
00117 if( d->lotChangedPosition == pos )
00118 return;
00119
00120 d->lotChangedPosition = pos;
00121 update();
00122 }
00123
00127 Qt::Alignment LeveyJenningsDiagram::lotChangedSymbolPosition() const
00128 {
00129 return d->lotChangedPosition;
00130 }
00131
00136 void LeveyJenningsDiagram::setFluidicsPackChangedSymbolPosition( Qt::Alignment pos )
00137 {
00138 if( d->fluidicsPackChangedPosition == pos )
00139 return;
00140
00141 d->fluidicsPackChangedPosition = pos;
00142 update();
00143 }
00144
00148 Qt::Alignment LeveyJenningsDiagram::fluidicsPackChangedSymbolPosition() const
00149 {
00150 return d->fluidicsPackChangedPosition;
00151 }
00152
00157 void LeveyJenningsDiagram::setSensorChangedSymbolPosition( Qt::Alignment pos )
00158 {
00159 if( d->sensorChangedPosition == pos )
00160 return;
00161
00162 d->sensorChangedPosition = pos;
00163 update();
00164 }
00165
00169 Qt::Alignment LeveyJenningsDiagram::sensorChangedSymbolPosition() const
00170 {
00171 return d->sensorChangedPosition;
00172 }
00173
00177 void LeveyJenningsDiagram::setFluidicsPackChanges( const QVector< QDateTime >& changes )
00178 {
00179 if( d->fluidicsPackChanges == changes )
00180 return;
00181
00182 d->fluidicsPackChanges = changes;
00183 update();
00184 }
00185
00189 QVector< QDateTime > LeveyJenningsDiagram::fluidicsPackChanges() const
00190 {
00191 return d->fluidicsPackChanges;
00192 }
00193
00197 void LeveyJenningsDiagram::setSensorChanges( const QVector< QDateTime >& changes )
00198 {
00199 if( d->sensorChanges == changes )
00200 return;
00201
00202 d->sensorChanges = changes;
00203 update();
00204 }
00205
00209 void LeveyJenningsDiagram::setScanLinePen( const QPen& pen )
00210 {
00211 if( d->scanLinePen == pen )
00212 return;
00213
00214 d->scanLinePen = pen;
00215 update();
00216 }
00217
00221 QPen LeveyJenningsDiagram::scanLinePen() const
00222 {
00223 return d->scanLinePen;
00224 }
00225
00229 QString LeveyJenningsDiagram::symbol( Symbol symbol ) const
00230 {
00231 return d->icons[ symbol ];
00232 }
00233
00237 void LeveyJenningsDiagram::setSymbol( Symbol symbol, const QString& filename )
00238 {
00239 if( d->icons[ symbol ] == filename )
00240 return;
00241
00242 delete d->iconRenderer[ symbol ];
00243 d->iconRenderer[ symbol ] = 0;
00244
00245 d->icons[ symbol ] = filename;
00246
00247 update();
00248 }
00249
00253 QVector< QDateTime > LeveyJenningsDiagram::sensorChanges() const
00254 {
00255 return d->sensorChanges;
00256 }
00257
00261 void LeveyJenningsDiagram::setExpectedMeanValue( float meanValue )
00262 {
00263 if( d->expectedMeanValue == meanValue )
00264 return;
00265
00266 d->expectedMeanValue = meanValue;
00267 d->setYAxisRange();
00268 update();
00269 }
00270
00274 float LeveyJenningsDiagram::expectedMeanValue() const
00275 {
00276 return d->expectedMeanValue;
00277 }
00278
00282 void LeveyJenningsDiagram::setExpectedStandardDeviation( float sd )
00283 {
00284 if( d->expectedStandardDeviation == sd )
00285 return;
00286
00287 d->expectedStandardDeviation = sd;
00288 d->setYAxisRange();
00289 update();
00290 }
00291
00295 float LeveyJenningsDiagram::expectedStandardDeviation() const
00296 {
00297 return d->expectedStandardDeviation;
00298 }
00299
00303 float LeveyJenningsDiagram::calculatedMeanValue() const
00304 {
00305 return d->calculatedMeanValue;
00306 }
00307
00311 float LeveyJenningsDiagram::calculatedStandardDeviation() const
00312 {
00313 return d->calculatedStandardDeviation;
00314 }
00315
00316 void LeveyJenningsDiagram::setModel( QAbstractItemModel* model )
00317 {
00318 if( this->model() != 0 )
00319 {
00320 disconnect( this->model(), SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ),
00321 this, SLOT( calculateMeanAndStandardDeviation() ) );
00322 disconnect( this->model(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ),
00323 this, SLOT( calculateMeanAndStandardDeviation() ) );
00324 disconnect( this->model(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ),
00325 this, SLOT( calculateMeanAndStandardDeviation() ) );
00326 disconnect( this->model(), SIGNAL( columnsInserted( const QModelIndex&, int, int ) ),
00327 this, SLOT( calculateMeanAndStandardDeviation() ) );
00328 disconnect( this->model(), SIGNAL( columnsRemoved( const QModelIndex&, int, int ) ),
00329 this, SLOT( calculateMeanAndStandardDeviation() ) );
00330 disconnect( this->model(), SIGNAL( modelReset() ),
00331 this, SLOT( calculateMeanAndStandardDeviation() ) );
00332 disconnect( this->model(), SIGNAL( layoutChanged() ),
00333 this, SLOT( calculateMeanAndStandardDeviation() ) );
00334 }
00335 LineDiagram::setModel( model );
00336 if( this->model() != 0 )
00337 {
00338 connect( this->model(), SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ),
00339 this, SLOT( calculateMeanAndStandardDeviation() ) );
00340 connect( this->model(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ),
00341 this, SLOT( calculateMeanAndStandardDeviation() ) );
00342 connect( this->model(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ),
00343 this, SLOT( calculateMeanAndStandardDeviation() ) );
00344 connect( this->model(), SIGNAL( columnsInserted( const QModelIndex&, int, int ) ),
00345 this, SLOT( calculateMeanAndStandardDeviation() ) );
00346 connect( this->model(), SIGNAL( columnsRemoved( const QModelIndex&, int, int ) ),
00347 this, SLOT( calculateMeanAndStandardDeviation() ) );
00348 connect( this->model(), SIGNAL( modelReset() ),
00349 this, SLOT( calculateMeanAndStandardDeviation() ) );
00350 connect( this->model(), SIGNAL( layoutChanged() ),
00351 this, SLOT( calculateMeanAndStandardDeviation() ) );
00352
00353 calculateMeanAndStandardDeviation();
00354 }
00355 }
00356
00357
00358
00359 void LeveyJenningsDiagram::calculateMeanAndStandardDeviation() const
00360 {
00361 QVector< double > values;
00362
00363 const QAbstractItemModel& m = *model();
00364 const int rowCount = m.rowCount( rootIndex() );
00365
00366 for( int row = 0; row < rowCount; ++row )
00367 {
00368 const QVariant var = m.data( m.index( row, 1, rootIndex() ) );
00369 if( !var.isValid() )
00370 continue;
00371 const double value = var.toDouble();
00372 if( ISNAN( value ) )
00373 continue;
00374 values << value;
00375 }
00376
00377 double sum = 0.0;
00378 double sumSquares = 0.0;
00379 KDAB_FOREACH( double value, values )
00380 {
00381 sum += value;
00382 sumSquares += value * value;
00383 }
00384
00385 const int N = values.count();
00386
00387 d->calculatedMeanValue = sum / N;
00388 d->calculatedStandardDeviation = sqrt( ( static_cast< double >( N ) * sumSquares - sum * sum ) / ( N * ( N - 1 ) ) );
00389 }
00390
00391
00392 static QDate floorDay( const QDateTime& dt )
00393 {
00394 return dt.date();
00395 }
00396
00397
00398 static QDate ceilDay( const QDateTime& dt )
00399 {
00400 QDate result = dt.date();
00401
00402 if( QDateTime( result, QTime() ) < dt )
00403 result = result.addDays( 1 );
00404
00405 return result;
00406 }
00407
00408
00409 static QDateTime floorHour( const QDateTime& dt )
00410 {
00411 return QDateTime( dt.date(), QTime( dt.time().hour(), 0 ) );
00412 }
00413
00414
00415 static QDateTime ceilHour( const QDateTime& dt )
00416 {
00417 QDateTime result( dt.date(), QTime( dt.time().hour(), 0 ) );
00418
00419 if( result < dt )
00420 result = result.addSecs( 3600 );
00421
00422 return result;
00423 }
00424
00426 const QPair<QPointF, QPointF> LeveyJenningsDiagram::calculateDataBoundaries() const
00427 {
00428 const double yMin = d->expectedMeanValue - 4 * d->expectedStandardDeviation;
00429 const double yMax = d->expectedMeanValue + 4 * d->expectedStandardDeviation;
00430
00431 d->setYAxisRange();
00432
00433
00434 const QPair< QDateTime, QDateTime > range = timeRange();
00435 const unsigned int minTime = range.first.toTime_t();
00436 const unsigned int maxTime = range.second.toTime_t();
00437
00438 const double xMin = minTime / static_cast< double >( 24 * 60 * 60 );
00439 const double xMax = maxTime / static_cast< double >( 24 * 60 * 60 ) - xMin;
00440
00441 const QPointF bottomLeft( QPointF( 0, yMin ) );
00442 const QPointF topRight( QPointF( xMax, yMax ) );
00443
00444 return QPair< QPointF, QPointF >( bottomLeft, topRight );
00445 }
00446
00450 QPair< QDateTime, QDateTime > LeveyJenningsDiagram::timeRange() const
00451 {
00452 if( d->timeRange != QPair< QDateTime, QDateTime >() )
00453 return d->timeRange;
00454
00455 const QAbstractItemModel& m = *model();
00456 const int rowCount = m.rowCount( rootIndex() );
00457
00458 const QDateTime begin = m.data( m.index( 0, 3, rootIndex() ) ).toDateTime();
00459 const QDateTime end = m.data( m.index( rowCount - 1, 3, rootIndex() ) ).toDateTime();
00460
00461 if( begin.secsTo( end ) > 86400 )
00462 {
00463
00464
00465 const QDate min = floorDay( begin );
00466 const QDate max = ceilDay( end );
00467 return QPair< QDateTime, QDateTime >( QDateTime( min ), QDateTime( max ) );
00468 }
00469 else if( begin.secsTo( end ) > 3600 )
00470 {
00471
00472
00473 const QDateTime min = floorHour( begin );
00474 const QDateTime max = ceilHour( end );
00475 return QPair< QDateTime, QDateTime >( min, max );
00476 }
00477 return QPair< QDateTime, QDateTime >( begin, end );
00478 }
00479
00484 void LeveyJenningsDiagram::setTimeRange( const QPair< QDateTime, QDateTime >& timeRange )
00485 {
00486 if( d->timeRange == timeRange )
00487 return;
00488
00489 d->timeRange = timeRange;
00490 update();
00491 }
00492
00496 void LeveyJenningsDiagram::drawChanges( PaintContext* ctx )
00497 {
00498 const unsigned int minTime = timeRange().first.toTime_t();
00499
00500 KDAB_FOREACH( const QDateTime& dt, d->fluidicsPackChanges )
00501 {
00502 const double xValue = ( dt.toTime_t() - minTime ) / static_cast< double >( 24 * 60 * 60 );
00503 const QPointF point( xValue, 0.0 );
00504 drawFluidicsPackChangedSymbol( ctx, point );
00505 }
00506
00507 KDAB_FOREACH( const QDateTime& dt, d->sensorChanges )
00508 {
00509 const double xValue = ( dt.toTime_t() - minTime ) / static_cast< double >( 24 * 60 * 60 );
00510 const QPointF point( xValue, 0.0 );
00511 drawSensorChangedSymbol( ctx, point );
00512 }
00513 }
00514
00516 void LeveyJenningsDiagram::paint( PaintContext* ctx )
00517 {
00518 d->reverseMapper.clear();
00519
00520
00521
00522 if ( !checkInvariants( true ) ) return;
00523 if ( !AbstractGrid::isBoundariesValid(dataBoundaries()) ) return;
00524
00525 QPainter* const painter = ctx->painter();
00526 const PainterSaver p( painter );
00527 if( model()->rowCount( rootIndex() ) == 0 || model()->columnCount( rootIndex() ) < 4 )
00528 return;
00529
00530 AbstractCoordinatePlane* const plane = ctx->coordinatePlane();
00531 ctx->setCoordinatePlane( plane->sharedAxisMasterPlane( painter ) );
00532
00533 const QAbstractItemModel& m = *model();
00534 const int rowCount = m.rowCount( rootIndex() );
00535
00536 const unsigned int minTime = timeRange().first.toTime_t();
00537
00538 painter->setRenderHint( QPainter::Antialiasing, true );
00539
00540 int prevLot = -1;
00541 QPointF prevPoint;
00542 bool hadMissingValue = false;
00543
00544 for( int row = 0; row < rowCount; ++row )
00545 {
00546 const QModelIndex lotIndex = m.index( row, 0, rootIndex() );
00547 const QModelIndex valueIndex = m.index( row, 1, rootIndex() );
00548 const QModelIndex okIndex = m.index( row, 2, rootIndex() );
00549 const QModelIndex timeIndex = m.index( row, 3, rootIndex() );
00550 const QModelIndex expectedMeanIndex = m.index( row, 4, rootIndex() );
00551 const QModelIndex expectedSDIndex = m.index( row, 5, rootIndex() );
00552
00553 painter->setPen( pen( lotIndex ) );
00554
00555 const int lot = m.data( lotIndex ).toInt();
00556 double value = m.data( valueIndex ).toDouble();
00557 const bool ok = m.data( okIndex ).toBool();
00558 const QDateTime time = m.data( timeIndex ).toDateTime();
00559 const double xValue = ( time.toTime_t() - minTime ) / static_cast< double >( 24 * 60 * 60 );
00560
00561 const double expectedMean = m.data( expectedMeanIndex ).toDouble();
00562 const double expectedSD = m.data( expectedSDIndex ).toDouble();
00563
00564 QPointF point = ctx->coordinatePlane()->translate( QPointF( xValue, value ) );
00565
00566 if( static_cast< int >( value ) == 0 )
00567 {
00568 hadMissingValue = true;
00569 }
00570 else
00571 {
00572 if( static_cast< int >( expectedMean ) != 0 && static_cast< int >( expectedSD ) != 0 )
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 }