KD Chart 2  [rev.2.5.1]
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Pages
KDChartLeveyJenningsDiagram.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 ** Copyright (C) 2001-2013 Klaralvdalens Datakonsult AB. All rights reserved.
3 **
4 ** This file is part of the KD Chart library.
5 **
6 ** Licensees holding valid commercial KD Chart licenses may use this file in
7 ** accordance with the KD Chart Commercial License Agreement provided with
8 ** the Software.
9 **
10 **
11 ** This file may be distributed and/or modified under the terms of the
12 ** GNU General Public License version 2 and version 3 as published by the
13 ** Free Software Foundation and appearing in the file LICENSE.GPL.txt included.
14 **
15 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
16 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
17 **
18 ** Contact info@kdab.com if any conditions of this licensing are not
19 ** clear to you.
20 **
21 **********************************************************************/
22 
24 #include "KDChartLeveyJenningsDiagram_p.h"
25 
26 #include "KDChartChart.h"
27 #include "KDChartTextAttributes.h"
28 #include "KDChartAbstractGrid.h"
29 #include "KDChartPainterSaver_p.h"
30 
31 #include <QDateTime>
32 #include <QFontMetrics>
33 #include <QPainter>
34 #include <QSvgRenderer>
35 #include <QVector>
36 
37 #include <KDABLibFakes>
38 
39 using namespace KDChart;
40 using namespace std;
41 
42 LeveyJenningsDiagram::Private::Private()
43 {
44 }
45 
46 LeveyJenningsDiagram::Private::~Private() {}
47 
48 
49 #define d d_func()
50 
51 
53  : LineDiagram( new Private(), parent, plane )
54 {
55  init();
56 }
57 
58 void LeveyJenningsDiagram::init()
59 {
60  d->lotChangedPosition = Qt::AlignTop;
61  d->fluidicsPackChangedPosition = Qt::AlignBottom;
62  d->sensorChangedPosition = Qt::AlignBottom;
63 
64  d->scanLinePen = QPen( Qt::blue );
65  setPen( d->scanLinePen );
66 
67  d->expectedMeanValue = 0.0;
68  d->expectedStandardDeviation = 0.0;
69 
70  d->diagram = this;
71 
72  d->icons[ LotChanged ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/karo_black.svg" );
73  d->icons[ SensorChanged ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/karo_red.svg" );
74  d->icons[ FluidicsPackChanged ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/karo_blue.svg" );
75  d->icons[ OkDataPoint ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/circle_blue.svg" );
76  d->icons[ NotOkDataPoint ] = QString::fromLatin1( ":/KDAB/kdchart/LeveyJennings/circle_blue_red.svg" );
77 
78  setSelectionMode( QAbstractItemView::SingleSelection );
79 }
80 
82 {
83 }
84 
89 {
90  LeveyJenningsDiagram* newDiagram = new LeveyJenningsDiagram( new Private( *d ) );
91  return newDiagram;
92 }
93 
95 {
96  if ( other == this ) return true;
97  if ( ! other ) {
98  return false;
99  }
100  /*
101  qDebug() <<"\n LineDiagram::compare():";
102  // compare own properties
103  qDebug() << (type() == other->type());
104  */
105  return // compare the base class
106  ( static_cast<const LineDiagram*>(this)->compare( other ) );
107 }
108 
114 {
115  if ( d->lotChangedPosition == pos )
116  return;
117 
118  d->lotChangedPosition = pos;
119  update();
120 }
121 
126 {
127  return d->lotChangedPosition;
128 }
129 
135 {
136  if ( d->fluidicsPackChangedPosition == pos )
137  return;
138 
139  d->fluidicsPackChangedPosition = pos;
140  update();
141 }
142 
147 {
148  return d->fluidicsPackChangedPosition;
149 }
150 
156 {
157  if ( d->sensorChangedPosition == pos )
158  return;
159 
160  d->sensorChangedPosition = pos;
161  update();
162 }
163 
168 {
169  return d->sensorChangedPosition;
170 }
171 
176 {
177  if ( d->fluidicsPackChanges == changes )
178  return;
179 
180  d->fluidicsPackChanges = changes;
181  update();
182 }
183 
188 {
189  return d->fluidicsPackChanges;
190 }
191 
196 {
197  if ( d->sensorChanges == changes )
198  return;
199 
200  d->sensorChanges = changes;
201  update();
202 }
203 
208 {
209  if ( d->scanLinePen == pen )
210  return;
211 
212  d->scanLinePen = pen;
213  update();
214 }
215 
220 {
221  return d->scanLinePen;
222 }
223 
227 QString LeveyJenningsDiagram::symbol( Symbol symbol ) const
228 {
229  return d->icons[ symbol ];
230 }
231 
235 void LeveyJenningsDiagram::setSymbol( Symbol symbol, const QString& filename )
236 {
237  if ( d->icons[ symbol ] == filename )
238  return;
239 
240  delete d->iconRenderer[ symbol ];
241  d->iconRenderer[ symbol ] = 0;
242 
243  d->icons[ symbol ] = filename;
244 
245  update();
246 }
247 
252 {
253  return d->sensorChanges;
254 }
255 
260 {
261  if ( d->expectedMeanValue == meanValue )
262  return;
263 
264  d->expectedMeanValue = meanValue;
265  d->setYAxisRange();
266  update();
267 }
268 
273 {
274  return d->expectedMeanValue;
275 }
276 
281 {
282  if ( d->expectedStandardDeviation == sd )
283  return;
284 
285  d->expectedStandardDeviation = sd;
286  d->setYAxisRange();
287  update();
288 }
289 
294 {
295  return d->expectedStandardDeviation;
296 }
297 
302 {
303  return d->calculatedMeanValue;
304 }
305 
310 {
311  return d->calculatedStandardDeviation;
312 }
313 
314 void LeveyJenningsDiagram::setModel( QAbstractItemModel* model )
315 {
316  if ( this->model() != 0 )
317  {
318  disconnect( this->model(), SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ),
319  this, SLOT( calculateMeanAndStandardDeviation() ) );
320  disconnect( this->model(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ),
321  this, SLOT( calculateMeanAndStandardDeviation() ) );
322  disconnect( this->model(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ),
323  this, SLOT( calculateMeanAndStandardDeviation() ) );
324  disconnect( this->model(), SIGNAL( columnsInserted( const QModelIndex&, int, int ) ),
325  this, SLOT( calculateMeanAndStandardDeviation() ) );
326  disconnect( this->model(), SIGNAL( columnsRemoved( const QModelIndex&, int, int ) ),
327  this, SLOT( calculateMeanAndStandardDeviation() ) );
328  disconnect( this->model(), SIGNAL( modelReset() ),
329  this, SLOT( calculateMeanAndStandardDeviation() ) );
330  disconnect( this->model(), SIGNAL( layoutChanged() ),
331  this, SLOT( calculateMeanAndStandardDeviation() ) );
332  }
333  LineDiagram::setModel( model );
334  if ( this->model() != 0 )
335  {
336  connect( this->model(), SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ),
337  this, SLOT( calculateMeanAndStandardDeviation() ) );
338  connect( this->model(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ),
339  this, SLOT( calculateMeanAndStandardDeviation() ) );
340  connect( this->model(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ),
341  this, SLOT( calculateMeanAndStandardDeviation() ) );
342  connect( this->model(), SIGNAL( columnsInserted( const QModelIndex&, int, int ) ),
343  this, SLOT( calculateMeanAndStandardDeviation() ) );
344  connect( this->model(), SIGNAL( columnsRemoved( const QModelIndex&, int, int ) ),
345  this, SLOT( calculateMeanAndStandardDeviation() ) );
346  connect( this->model(), SIGNAL( modelReset() ),
347  this, SLOT( calculateMeanAndStandardDeviation() ) );
348  connect( this->model(), SIGNAL( layoutChanged() ),
349  this, SLOT( calculateMeanAndStandardDeviation() ) );
350 
352  }
353 }
354 
355 // TODO: This is the 'easy' solution
356 // evaluate whether this is enough or we need some better one or even boost here
358 {
359  QVector< qreal > values;
360  // first fetch all values
361  const QAbstractItemModel& m = *model();
362  const int rowCount = m.rowCount( rootIndex() );
363 
364  for ( int row = 0; row < rowCount; ++row )
365  {
366  const QVariant var = m.data( m.index( row, 1, rootIndex() ) );
367  if ( !var.isValid() )
368  continue;
369  const qreal value = var.toReal();
370  if ( ISNAN( value ) )
371  continue;
372  values << value;
373  }
374 
375  qreal sum = 0.0;
376  qreal sumSquares = 0.0;
377  KDAB_FOREACH( qreal value, values )
378  {
379  sum += value;
380  sumSquares += value * value;
381  }
382 
383  const int N = values.count();
384 
385  d->calculatedMeanValue = sum / N;
386  d->calculatedStandardDeviation = sqrt( ( static_cast< qreal >( N ) * sumSquares - sum * sum ) / ( N * ( N - 1 ) ) );
387 }
388 
389 // calculates the largest QDate not greater than \a dt.
390 static QDate floorDay( const QDateTime& dt )
391 {
392  return dt.date();
393 }
394 
395 // calculates the smallest QDate not less than \a dt.
396 static QDate ceilDay( const QDateTime& dt )
397 {
398  QDate result = dt.date();
399 
400  if ( QDateTime( result, QTime() ) < dt )
401  result = result.addDays( 1 );
402 
403  return result;
404 }
405 
406 // calculates the largest QDateTime like xx:00 not greater than \a dt.
407 static QDateTime floorHour( const QDateTime& dt )
408 {
409  return QDateTime( dt.date(), QTime( dt.time().hour(), 0 ) );
410 }
411 
412 // calculates the smallest QDateTime like xx:00 not less than \a dt.
413 static QDateTime ceilHour( const QDateTime& dt )
414 {
415  QDateTime result( dt.date(), QTime( dt.time().hour(), 0 ) );
416 
417  if ( result < dt )
418  result = result.addSecs( 3600 );
419 
420  return result;
421 }
422 
425 {
426  const qreal yMin = d->expectedMeanValue - 4 * d->expectedStandardDeviation;
427  const qreal yMax = d->expectedMeanValue + 4 * d->expectedStandardDeviation;
428 
429  d->setYAxisRange();
430 
431  // rounded down/up to the prev/next midnight (at least that's the default)
433  const unsigned int minTime = range.first.toTime_t();
434  const unsigned int maxTime = range.second.toTime_t();
435 
436  const qreal xMin = minTime / static_cast< qreal >( 24 * 60 * 60 );
437  const qreal xMax = maxTime / static_cast< qreal >( 24 * 60 * 60 ) - xMin;
438 
439  const QPointF bottomLeft( QPointF( 0, yMin ) );
440  const QPointF topRight( QPointF( xMax, yMax ) );
441 
442  return QPair< QPointF, QPointF >( bottomLeft, topRight );
443 }
444 
449 {
450  if ( d->timeRange != QPair< QDateTime, QDateTime >() )
451  return d->timeRange;
452 
453  const QAbstractItemModel& m = *model();
454  const int rowCount = m.rowCount( rootIndex() );
455 
456  const QDateTime begin = m.data( m.index( 0, 3, rootIndex() ) ).toDateTime();
457  const QDateTime end = m.data( m.index( rowCount - 1, 3, rootIndex() ) ).toDateTime();
458 
459  if ( begin.secsTo( end ) > 86400 )
460  {
461  // if begin to end is more than 24h
462  // round down/up to the prev/next midnight
463  const QDate min = floorDay( begin );
464  const QDate max = ceilDay( end );
465  return QPair< QDateTime, QDateTime >( QDateTime( min ), QDateTime( max ) );
466  }
467  else if ( begin.secsTo( end ) > 3600 )
468  {
469  // more than 1h: rond down up to the prex/next hour
470  // if begin to end is more than 24h
471  const QDateTime min = floorHour( begin );
472  const QDateTime max = ceilHour( end );
473  return QPair< QDateTime, QDateTime >( min, max );
474  }
475  return QPair< QDateTime, QDateTime >( begin, end );
476 }
477 
483 {
484  if ( d->timeRange == timeRange )
485  return;
486 
487  d->timeRange = timeRange;
488  update();
489 }
490 
495 {
496  const unsigned int minTime = timeRange().first.toTime_t();
497 
498  KDAB_FOREACH( const QDateTime& dt, d->fluidicsPackChanges )
499  {
500  const qreal xValue = ( dt.toTime_t() - minTime ) / static_cast< qreal >( 24 * 60 * 60 );
501  const QPointF point( xValue, 0.0 );
502  drawFluidicsPackChangedSymbol( ctx, point );
503  }
504 
505  KDAB_FOREACH( const QDateTime& dt, d->sensorChanges )
506  {
507  const qreal xValue = ( dt.toTime_t() - minTime ) / static_cast< qreal >( 24 * 60 * 60 );
508  const QPointF point( xValue, 0.0 );
509  drawSensorChangedSymbol( ctx, point );
510  }
511 }
512 
515 {
516  d->reverseMapper.clear();
517 
518  // note: Not having any data model assigned is no bug
519  // but we can not draw a diagram then either.
520  if ( !checkInvariants( true ) ) return;
521  if ( !AbstractGrid::isBoundariesValid(dataBoundaries()) ) return;
522 
523  QPainter* const painter = ctx->painter();
524  const PainterSaver p( painter );
525  if ( model()->rowCount( rootIndex() ) == 0 || model()->columnCount( rootIndex() ) < 4 )
526  return; // nothing to paint for us
527 
528  AbstractCoordinatePlane* const plane = ctx->coordinatePlane();
529  ctx->setCoordinatePlane( plane->sharedAxisMasterPlane( painter ) );
530 
531  const QAbstractItemModel& m = *model();
532  const int rowCount = m.rowCount( rootIndex() );
533 
534  const unsigned int minTime = timeRange().first.toTime_t();
535 
536  painter->setRenderHint( QPainter::Antialiasing, true );
537 
538  int prevLot = -1;
539  QPointF prevPoint;
540  bool hadMissingValue = false;
541 
542  for ( int row = 0; row < rowCount; ++row )
543  {
544  const QModelIndex lotIndex = m.index( row, 0, rootIndex() );
545  const QModelIndex valueIndex = m.index( row, 1, rootIndex() );
546  const QModelIndex okIndex = m.index( row, 2, rootIndex() );
547  const QModelIndex timeIndex = m.index( row, 3, rootIndex() );
548  const QModelIndex expectedMeanIndex = m.index( row, 4, rootIndex() );
549  const QModelIndex expectedSDIndex = m.index( row, 5, rootIndex() );
550 
551  painter->setPen( pen( lotIndex ) );
552 
553  QVariant vValue = m.data( valueIndex );
554  qreal value = vValue.toReal();
555  const int lot = m.data( lotIndex ).toInt();
556  const bool ok = m.data( okIndex ).toBool();
557  const QDateTime time = m.data( timeIndex ).toDateTime();
558  const qreal xValue = ( time.toTime_t() - minTime ) / static_cast< qreal >( 24 * 60 * 60 );
559 
560  QVariant vExpectedMean = m.data( expectedMeanIndex );
561  const qreal expectedMean = vExpectedMean.toReal();
562  QVariant vExpectedSD = m.data( expectedSDIndex );
563  const qreal expectedSD = vExpectedSD.toReal();
564 
565  QPointF point = ctx->coordinatePlane()->translate( QPointF( xValue, value ) );
566 
567  if ( vValue.isNull() )
568  {
569  hadMissingValue = true;
570  }
571  else
572  {
573  if ( !vExpectedMean.isNull() && !vExpectedSD.isNull() )
574  {
575  // this calculates the 'logical' value relative to the expected mean and SD of this point
576  value -= expectedMean;
577  value /= expectedSD;
578  value *= d->expectedStandardDeviation;
579  value += d->expectedMeanValue;
580  point = ctx->coordinatePlane()->translate( QPointF( xValue, value ) );
581  }
582 
583  if ( prevLot == lot )
584  {
585  const QPen pen = painter->pen();
586  QPen newPen = pen;
587 
588  if ( hadMissingValue )
589  {
590  newPen.setDashPattern( QVector< qreal >() << 4.0 << 4.0 );
591  }
592 
593  painter->setPen( newPen );
594  painter->drawLine( prevPoint, point );
595  painter->setPen( pen );
596  // d->reverseMapper.addLine( valueIndex.row(), valueIndex.column(), prevPoint, point );
597  }
598  else if ( row > 0 )
599  {
600  drawLotChangeSymbol( ctx, QPointF( xValue, value ) );
601  }
602 
603  if ( value <= d->expectedMeanValue + 4 * d->expectedStandardDeviation &&
604  value >= d->expectedMeanValue - 4 * d->expectedStandardDeviation )
605  {
606  const QPointF location( xValue, value );
607  drawDataPointSymbol( ctx, location, ok );
608  d->reverseMapper.addCircle( valueIndex.row(),
609  valueIndex.column(),
610  ctx->coordinatePlane()->translate( location ),
611  iconRect().size() );
612  }
613  prevLot = lot;
614  prevPoint = point;
615  hadMissingValue = false;
616  }
617 
618  const QModelIndex current = selectionModel()->currentIndex();
619  if ( selectionModel()->rowIntersectsSelection( lotIndex.row(), lotIndex.parent() ) || current.sibling( current.row(), 0 ) == lotIndex )
620  {
621  const QPen pen = ctx->painter()->pen();
622  painter->setPen( d->scanLinePen );
623  painter->drawLine( ctx->coordinatePlane()->translate( QPointF( xValue, d->expectedMeanValue - 4 *
624  d->expectedStandardDeviation ) ),
625  ctx->coordinatePlane()->translate( QPointF( xValue, d->expectedMeanValue + 4 *
626  d->expectedStandardDeviation ) ) );
627  painter->setPen( pen );
628  }
629  }
630 
631  drawChanges( ctx );
632 
633  ctx->setCoordinatePlane( plane );
634 }
635 
641 void LeveyJenningsDiagram::drawDataPointSymbol( PaintContext* ctx, const QPointF& pos, bool ok )
642 {
643  const Symbol type = ok ? OkDataPoint : NotOkDataPoint;
644 
645  QPainter* const painter = ctx->painter();
646  const PainterSaver ps( painter );
647  const QPointF transPos = ctx->coordinatePlane()->translate( pos ).toPoint();
648  painter->translate( transPos );
649 
650  painter->setClipping( false );
651  iconRenderer( type )->render( painter, iconRect() );
652 }
653 
660 {
661  const QPointF transPos = ctx->coordinatePlane()->translate(
662  QPointF( pos.x(), d->lotChangedPosition & Qt::AlignTop ? d->expectedMeanValue +
663  4 * d->expectedStandardDeviation
664  : d->expectedMeanValue -
665  4 * d->expectedStandardDeviation ) );
666 
667 
668  QPainter* const painter = ctx->painter();
669  const PainterSaver ps( painter );
670  painter->setClipping( false );
671  painter->translate( transPos );
672  iconRenderer( LotChanged )->render( painter, iconRect() );
673 }
674 
681 {
682  const QPointF transPos = ctx->coordinatePlane()->translate(
683  QPointF( pos.x(), d->sensorChangedPosition & Qt::AlignTop ? d->expectedMeanValue +
684  4 * d->expectedStandardDeviation
685  : d->expectedMeanValue -
686  4 * d->expectedStandardDeviation ) );
687 
688  QPainter* const painter = ctx->painter();
689  const PainterSaver ps( painter );
690  painter->setClipping( false );
691  painter->translate( transPos );
692  iconRenderer( SensorChanged )->render( painter, iconRect() );
693 }
694 
701 {
702  const QPointF transPos = ctx->coordinatePlane()->translate(
703  QPointF( pos.x(), d->fluidicsPackChangedPosition & Qt::AlignTop ? d->expectedMeanValue +
704  4 * d->expectedStandardDeviation
705  : d->expectedMeanValue -
706  4 * d->expectedStandardDeviation ) );
707 
708  QPainter* const painter = ctx->painter();
709  const PainterSaver ps( painter );
710  painter->setClipping( false );
711  painter->translate( transPos );
712  iconRenderer( FluidicsPackChanged )->render( painter, iconRect() );
713 }
714 
719 {
721  TextAttributes test;
722  test.setFontSize( m );
723  const QFontMetrics fm( test.calculatedFont( coordinatePlane()->parent(), KDChartEnums::MeasureOrientationAuto ) );
724  const qreal height = fm.height() / 1.2;
725  return QRectF( -height / 2.0, -height / 2.0, height, height );
726 }
727 
732 {
733  if ( d->iconRenderer[ symbol ] == 0 )
734  d->iconRenderer[ symbol ] = new QSvgRenderer( d->icons[ symbol ], this );
735 
736  return d->iconRenderer[ symbol ];
737 }

Klarälvdalens Datakonsult AB (KDAB)
Qt-related services and products
http://www.kdab.com/
http://www.kdab.com/products/kd-chart/