KD Chart API Documentation  3.1
KDChartLeveyJenningsDiagram.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** This file is part of the KD Chart library.
4 **
5 ** SPDX-FileCopyrightText: 2001 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
6 **
7 ** SPDX-License-Identifier: MIT
8 **
9 ****************************************************************************/
10 
12 #include "KDChartLeveyJenningsDiagram_p.h"
13 
14 #include "KDChartAbstractGrid.h"
15 #include "KDChartChart.h"
16 #include "KDChartPainterSaver_p.h"
17 #include "KDChartTextAttributes.h"
18 
19 #include <QDateTime>
20 #include <QFontMetrics>
21 #include <QPainter>
22 #include <QSvgRenderer>
23 #include <QVector>
24 
25 #include <KDABLibFakes>
26 
27 using namespace KDChart;
28 using namespace std;
29 
30 LeveyJenningsDiagram::Private::Private()
31 {
32 }
33 
34 LeveyJenningsDiagram::Private::~Private()
35 {
36 }
37 
38 #define d d_func()
39 
41  : LineDiagram(new Private(), parent, plane)
42 {
43  init();
44 }
45 
46 void LeveyJenningsDiagram::init()
47 {
48  d->lotChangedPosition = Qt::AlignTop;
49  d->fluidicsPackChangedPosition = Qt::AlignBottom;
50  d->sensorChangedPosition = Qt::AlignBottom;
51 
52  d->scanLinePen = QPen(Qt::blue);
53  setPen(d->scanLinePen);
54 
55  d->expectedMeanValue = 0.0;
56  d->expectedStandardDeviation = 0.0;
57 
58  d->diagram = this;
59 
60  d->icons[LotChanged] = QString::fromLatin1(":/KDAB/kdchart/LeveyJennings/karo_black.svg");
61  d->icons[SensorChanged] = QString::fromLatin1(":/KDAB/kdchart/LeveyJennings/karo_red.svg");
62  d->icons[FluidicsPackChanged] = QString::fromLatin1(":/KDAB/kdchart/LeveyJennings/karo_blue.svg");
63  d->icons[OkDataPoint] = QString::fromLatin1(":/KDAB/kdchart/LeveyJennings/circle_blue.svg");
64  d->icons[NotOkDataPoint] = QString::fromLatin1(":/KDAB/kdchart/LeveyJennings/circle_blue_red.svg");
65 
66  setSelectionMode(QAbstractItemView::SingleSelection);
67 }
68 
70 {
71 }
72 
77 {
78  auto *newDiagram = new LeveyJenningsDiagram(new Private(*d));
79  return newDiagram;
80 }
81 
83 {
84  if (other == this)
85  return true;
86  if (!other) {
87  return false;
88  }
89  /*
90  qDebug() <<"\n LineDiagram::compare():";
91  // compare own properties
92  qDebug() << (type() == other->type());
93  */
94  return // compare the base class
95  (static_cast<const LineDiagram *>(this)->compare(other));
96 }
97 
103 {
104  if (d->lotChangedPosition == pos)
105  return;
106 
107  d->lotChangedPosition = pos;
108  update();
109 }
110 
115 {
116  return d->lotChangedPosition;
117 }
118 
124 {
125  if (d->fluidicsPackChangedPosition == pos)
126  return;
127 
128  d->fluidicsPackChangedPosition = pos;
129  update();
130 }
131 
136 {
137  return d->fluidicsPackChangedPosition;
138 }
139 
145 {
146  if (d->sensorChangedPosition == pos)
147  return;
148 
149  d->sensorChangedPosition = pos;
150  update();
151 }
152 
157 {
158  return d->sensorChangedPosition;
159 }
160 
164 void LeveyJenningsDiagram::setFluidicsPackChanges(const QVector<QDateTime> &changes)
165 {
166  if (d->fluidicsPackChanges == changes)
167  return;
168 
169  d->fluidicsPackChanges = changes;
170  update();
171 }
172 
177 {
178  return d->fluidicsPackChanges;
179 }
180 
184 void LeveyJenningsDiagram::setSensorChanges(const QVector<QDateTime> &changes)
185 {
186  if (d->sensorChanges == changes)
187  return;
188 
189  d->sensorChanges = changes;
190  update();
191 }
192 
197 {
198  if (d->scanLinePen == pen)
199  return;
200 
201  d->scanLinePen = pen;
202  update();
203 }
204 
209 {
210  return d->scanLinePen;
211 }
212 
217 {
218  return d->icons[symbol];
219 }
220 
224 void LeveyJenningsDiagram::setSymbol(Symbol symbol, const QString &filename)
225 {
226  if (d->icons[symbol] == filename)
227  return;
228 
229  delete d->iconRenderer[symbol];
230  d->iconRenderer[symbol] = 0;
231 
232  d->icons[symbol] = filename;
233 
234  update();
235 }
236 
240 QVector<QDateTime> LeveyJenningsDiagram::sensorChanges() const
241 {
242  return d->sensorChanges;
243 }
244 
249 {
250  if (d->expectedMeanValue == meanValue)
251  return;
252 
253  d->expectedMeanValue = meanValue;
254  d->setYAxisRange();
255  update();
256 }
257 
262 {
263  return d->expectedMeanValue;
264 }
265 
270 {
271  if (d->expectedStandardDeviation == sd)
272  return;
273 
274  d->expectedStandardDeviation = sd;
275  d->setYAxisRange();
276  update();
277 }
278 
283 {
284  return d->expectedStandardDeviation;
285 }
286 
291 {
292  return d->calculatedMeanValue;
293 }
294 
299 {
300  return d->calculatedStandardDeviation;
301 }
302 
303 void LeveyJenningsDiagram::setModel(QAbstractItemModel *newModel)
304 {
305  QAbstractItemModel *oldModel = model();
306  if (oldModel != nullptr) {
307  disconnect(oldModel, &QAbstractItemModel::dataChanged,
309  disconnect(oldModel, &QAbstractItemModel::rowsInserted,
311  disconnect(oldModel, &QAbstractItemModel::rowsRemoved,
313  disconnect(oldModel, &QAbstractItemModel::columnsInserted,
315  disconnect(oldModel, &QAbstractItemModel::columnsRemoved,
317  disconnect(oldModel, &QAbstractItemModel::modelReset,
319  disconnect(oldModel, &QAbstractItemModel::layoutChanged,
321  }
322  LineDiagram::setModel(newModel);
323  if (newModel != nullptr) {
324  connect(newModel, &QAbstractItemModel::dataChanged,
326  connect(newModel, &QAbstractItemModel::rowsInserted,
328  connect(newModel, &QAbstractItemModel::rowsRemoved,
330  connect(newModel, &QAbstractItemModel::columnsInserted,
332  connect(newModel, &QAbstractItemModel::columnsRemoved,
334  connect(newModel, &QAbstractItemModel::modelReset,
336  connect(newModel, &QAbstractItemModel::layoutChanged,
338 
340  }
341 }
342 
343 // TODO: This is the 'easy' solution
344 // evaluate whether this is enough or we need some better one or even boost here
346 {
347  QVector<qreal> values;
348  // first fetch all values
349  const QAbstractItemModel &m = *model();
350  const int rowCount = m.rowCount(rootIndex());
351 
352  for (int row = 0; row < rowCount; ++row) {
353  const QVariant var = m.data(m.index(row, 1, rootIndex()));
354  if (!var.isValid())
355  continue;
356  const qreal value = var.toReal();
357  if (ISNAN(value))
358  continue;
359  values << value;
360  }
361 
362  qreal sum = 0.0;
363  qreal sumSquares = 0.0;
364  for (qreal value : qAsConst(values)) {
365  sum += value;
366  sumSquares += value * value;
367  }
368 
369  const int N = values.count();
370 
371  d->calculatedMeanValue = sum / N;
372  d->calculatedStandardDeviation = sqrt((static_cast<qreal>(N) * sumSquares - sum * sum) / (N * (N - 1)));
373 }
374 
375 // calculates the largest QDate not greater than \a dt.
376 static QDate floorDay(const QDateTime &dt)
377 {
378  return dt.date();
379 }
380 
381 // calculates the smallest QDate not less than \a dt.
382 static QDate ceilDay(const QDateTime &dt)
383 {
384  QDate result = dt.date();
385 
386  if (QDateTime(result, QTime()) < dt)
387  result = result.addDays(1);
388 
389  return result;
390 }
391 
392 // calculates the largest QDateTime like xx:00 not greater than \a dt.
393 static QDateTime floorHour(const QDateTime &dt)
394 {
395  return QDateTime(dt.date(), QTime(dt.time().hour(), 0));
396 }
397 
398 // calculates the smallest QDateTime like xx:00 not less than \a dt.
399 static QDateTime ceilHour(const QDateTime &dt)
400 {
401  QDateTime result(dt.date(), QTime(dt.time().hour(), 0));
402 
403  if (result < dt)
404  result = result.addSecs(3600);
405 
406  return result;
407 }
408 
410 const QPair<QPointF, QPointF> LeveyJenningsDiagram::calculateDataBoundaries() const
411 {
412  const qreal yMin = d->expectedMeanValue - 4 * d->expectedStandardDeviation;
413  const qreal yMax = d->expectedMeanValue + 4 * d->expectedStandardDeviation;
414 
415  d->setYAxisRange();
416 
417  // rounded down/up to the prev/next midnight (at least that's the default)
418  const QPair<QDateTime, QDateTime> range = timeRange();
419  const qint64 minTime = range.first.toSecsSinceEpoch();
420  const qint64 maxTime = range.second.toSecsSinceEpoch();
421 
422  const qreal xMin = static_cast<qreal>(minTime / 24 * 60 * 60);
423  const qreal xMax = static_cast<qreal>(maxTime / 24 * 60 * 60) - xMin;
424 
425  const QPointF bottomLeft(QPointF(0, yMin));
426  const QPointF topRight(QPointF(xMax, yMax));
427 
428  return QPair<QPointF, QPointF>(bottomLeft, topRight);
429 }
430 
434 QPair<QDateTime, QDateTime> LeveyJenningsDiagram::timeRange() const
435 {
436  if (d->timeRange != QPair<QDateTime, QDateTime>())
437  return d->timeRange;
438 
439  const QAbstractItemModel &m = *model();
440  const int rowCount = m.rowCount(rootIndex());
441 
442  const QDateTime begin = m.data(m.index(0, 3, rootIndex())).toDateTime();
443  const QDateTime end = m.data(m.index(rowCount - 1, 3, rootIndex())).toDateTime();
444 
445  if (begin.secsTo(end) > 86400) {
446  // if begin to end is more than 24h
447  // round down/up to the prev/next midnight
448  const QDate min = floorDay(begin);
449  const QDate max = ceilDay(end);
450  return QPair<QDateTime, QDateTime>(min.startOfDay(), max.startOfDay());
451  } else if (begin.secsTo(end) > 3600) {
452  // more than 1h: rond down up to the prex/next hour
453  // if begin to end is more than 24h
454  const QDateTime min = floorHour(begin);
455  const QDateTime max = ceilHour(end);
456  return QPair<QDateTime, QDateTime>(min, max);
457  }
458  return QPair<QDateTime, QDateTime>(begin, end);
459 }
460 
465 void LeveyJenningsDiagram::setTimeRange(const QPair<QDateTime, QDateTime> &timeRange)
466 {
467  if (d->timeRange == timeRange)
468  return;
469 
470  d->timeRange = timeRange;
471  update();
472 }
473 
478 {
479  const unsigned int minTime = timeRange().first.toSecsSinceEpoch();
480 
481  for (const QDateTime &dt : qAsConst(d->fluidicsPackChanges)) {
482  const qreal xValue = (dt.toSecsSinceEpoch() - minTime) / static_cast<qreal>(24 * 60 * 60);
483  const QPointF point(xValue, 0.0);
484  drawFluidicsPackChangedSymbol(ctx, point);
485  }
486 
487  for (const QDateTime &dt : qAsConst(d->sensorChanges)) {
488  const qreal xValue = (dt.toSecsSinceEpoch() - minTime) / static_cast<qreal>(24 * 60 * 60);
489  const QPointF point(xValue, 0.0);
490  drawSensorChangedSymbol(ctx, point);
491  }
492 }
493 
496 {
497  d->reverseMapper.clear();
498 
499  // note: Not having any data model assigned is no bug
500  // but we can not draw a diagram then either.
501  if (!checkInvariants(true))
502  return;
504  return;
505 
506  QPainter *const painter = ctx->painter();
507  const PainterSaver p(painter);
508  if (model()->rowCount(rootIndex()) == 0 || model()->columnCount(rootIndex()) < 4)
509  return; // nothing to paint for us
510 
511  AbstractCoordinatePlane *const plane = ctx->coordinatePlane();
512  ctx->setCoordinatePlane(plane->sharedAxisMasterPlane(painter));
513 
514  const QAbstractItemModel &m = *model();
515  const int rowCount = m.rowCount(rootIndex());
516 
517  const unsigned int minTime = timeRange().first.toSecsSinceEpoch();
518 
519  painter->setRenderHint(QPainter::Antialiasing, true);
520 
521  int prevLot = -1;
522  QPointF prevPoint;
523  bool hadMissingValue = false;
524 
525  for (int row = 0; row < rowCount; ++row) {
526  const QModelIndex lotIndex = m.index(row, 0, rootIndex());
527  const QModelIndex valueIndex = m.index(row, 1, rootIndex());
528  const QModelIndex okIndex = m.index(row, 2, rootIndex());
529  const QModelIndex timeIndex = m.index(row, 3, rootIndex());
530  const QModelIndex expectedMeanIndex = m.index(row, 4, rootIndex());
531  const QModelIndex expectedSDIndex = m.index(row, 5, rootIndex());
532 
533  painter->setPen(pen(lotIndex));
534 
535  QVariant vValue = m.data(valueIndex);
536  qreal value = vValue.toReal();
537  const int lot = m.data(lotIndex).toInt();
538  const bool ok = m.data(okIndex).toBool();
539  const QDateTime time = m.data(timeIndex).toDateTime();
540  const qreal xValue = (time.toSecsSinceEpoch() - minTime) / static_cast<qreal>(24 * 60 * 60);
541 
542  QVariant vExpectedMean = m.data(expectedMeanIndex);
543  const qreal expectedMean = vExpectedMean.toReal();
544  QVariant vExpectedSD = m.data(expectedSDIndex);
545  const qreal expectedSD = vExpectedSD.toReal();
546 
547  QPointF point = ctx->coordinatePlane()->translate(QPointF(xValue, value));
548 
549  if (vValue.isNull()) {
550  hadMissingValue = true;
551  } else {
552  if (!vExpectedMean.isNull() && !vExpectedSD.isNull()) {
553  // this calculates the 'logical' value relative to the expected mean and SD of this point
554  value -= expectedMean;
555  value /= expectedSD;
556  value *= d->expectedStandardDeviation;
557  value += d->expectedMeanValue;
558  point = ctx->coordinatePlane()->translate(QPointF(xValue, value));
559  }
560 
561  if (prevLot == lot) {
562  const QPen pen = painter->pen();
563  QPen newPen = pen;
564 
565  if (hadMissingValue) {
566  newPen.setDashPattern(QVector<qreal>() << 4.0 << 4.0);
567  }
568 
569  painter->setPen(newPen);
570  painter->drawLine(prevPoint, point);
571  painter->setPen(pen);
572  // d->reverseMapper.addLine( valueIndex.row(), valueIndex.column(), prevPoint, point );
573  } else if (row > 0) {
574  drawLotChangeSymbol(ctx, QPointF(xValue, value));
575  }
576 
577  if (value <= d->expectedMeanValue + 4 * d->expectedStandardDeviation && value >= d->expectedMeanValue - 4 * d->expectedStandardDeviation) {
578  const QPointF location(xValue, value);
579  drawDataPointSymbol(ctx, location, ok);
580  d->reverseMapper.addCircle(valueIndex.row(),
581  valueIndex.column(),
582  ctx->coordinatePlane()->translate(location),
583  iconRect().size());
584  }
585  prevLot = lot;
586  prevPoint = point;
587  hadMissingValue = false;
588  }
589 
590  const QModelIndex current = selectionModel()->currentIndex();
591  if (selectionModel()->rowIntersectsSelection(lotIndex.row(), lotIndex.parent()) || current.sibling(current.row(), 0) == lotIndex) {
592  const QPen pen = ctx->painter()->pen();
593  painter->setPen(d->scanLinePen);
594  painter->drawLine(ctx->coordinatePlane()->translate(QPointF(xValue, d->expectedMeanValue - 4 * d->expectedStandardDeviation)),
595  ctx->coordinatePlane()->translate(QPointF(xValue, d->expectedMeanValue + 4 * d->expectedStandardDeviation)));
596  painter->setPen(pen);
597  }
598  }
599 
600  drawChanges(ctx);
601 
602  ctx->setCoordinatePlane(plane);
603 }
604 
610 void LeveyJenningsDiagram::drawDataPointSymbol(PaintContext *ctx, const QPointF &pos, bool ok)
611 {
612  const Symbol type = ok ? OkDataPoint : NotOkDataPoint;
613 
614  QPainter *const painter = ctx->painter();
615  const PainterSaver ps(painter);
616  const QPointF transPos = ctx->coordinatePlane()->translate(pos).toPoint();
617  painter->translate(transPos);
618 
619  painter->setClipping(false);
620  iconRenderer(type)->render(painter, iconRect());
621 }
622 
629 {
630  const QPointF transPos = ctx->coordinatePlane()->translate(
631  QPointF(pos.x(), d->lotChangedPosition & Qt::AlignTop ? d->expectedMeanValue + 4 * d->expectedStandardDeviation : d->expectedMeanValue - 4 * d->expectedStandardDeviation));
632 
633  QPainter *const painter = ctx->painter();
634  const PainterSaver ps(painter);
635  painter->setClipping(false);
636  painter->translate(transPos);
637  iconRenderer(LotChanged)->render(painter, iconRect());
638 }
639 
646 {
647  const QPointF transPos = ctx->coordinatePlane()->translate(
648  QPointF(pos.x(), d->sensorChangedPosition & Qt::AlignTop ? d->expectedMeanValue + 4 * d->expectedStandardDeviation : d->expectedMeanValue - 4 * d->expectedStandardDeviation));
649 
650  QPainter *const painter = ctx->painter();
651  const PainterSaver ps(painter);
652  painter->setClipping(false);
653  painter->translate(transPos);
654  iconRenderer(SensorChanged)->render(painter, iconRect());
655 }
656 
663 {
664  const QPointF transPos = ctx->coordinatePlane()->translate(
665  QPointF(pos.x(), d->fluidicsPackChangedPosition & Qt::AlignTop ? d->expectedMeanValue + 4 * d->expectedStandardDeviation : d->expectedMeanValue - 4 * d->expectedStandardDeviation));
666 
667  QPainter *const painter = ctx->painter();
668  const PainterSaver ps(painter);
669  painter->setClipping(false);
670  painter->translate(transPos);
671  iconRenderer(FluidicsPackChanged)->render(painter, iconRect());
672 }
673 
678 {
680  TextAttributes test;
681  test.setFontSize(m);
682  const QFontMetrics fm(test.calculatedFont(coordinatePlane()->parent(), KDChartEnums::MeasureOrientationAuto));
683  const qreal height = fm.height() / 1.2;
684  return QRectF(-height / 2.0, -height / 2.0, height, height);
685 }
686 
691 {
692  if (d->iconRenderer[symbol] == 0)
693  d->iconRenderer[symbol] = new QSvgRenderer(d->icons[symbol], this);
694 
695  return d->iconRenderer[symbol];
696 }
static QDateTime floorHour(const QDateTime &dt)
static QDate floorDay(const QDateTime &dt)
static QDateTime ceilHour(const QDateTime &dt)
static QDate ceilDay(const QDateTime &dt)
@ MeasureCalculationModeAuto
Definition: KDChartEnums.h:217
@ MeasureOrientationAuto
Definition: KDChartEnums.h:287
void setModel(QAbstractItemModel *model) override
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane,...
virtual const QPointF translate(const QPointF &diagramPoint) const =0
virtual AbstractCoordinatePlane * sharedAxisMasterPlane(QPainter *p=nullptr)
virtual bool checkInvariants(bool justReturnTheStatus=false) const
void setPen(const QModelIndex &index, const QPen &pen)
const QPair< QPointF, QPointF > dataBoundaries() const
Return the bottom left and top right data point, that the diagram will display (unless the grid adjus...
AbstractCoordinatePlane * coordinatePlane() const
static bool isBoundariesValid(const QRectF &r)
Levey Jennings coordinate plane This is actually nothing real more than a plain cartesian coordinate ...
LeveyDiagram defines a Levey Jennings chart.
LeveyJenningsDiagram(QWidget *parent=nullptr, LeveyJenningsCoordinatePlane *plane=nullptr)
void setModel(QAbstractItemModel *model) override
void setLotChangedSymbolPosition(Qt::Alignment pos)
void setFluidicsPackChanges(const QVector< QDateTime > &changes)
void drawChanges(PaintContext *paintContext)
void setFluidicsPackChangedSymbolPosition(Qt::Alignment pos)
QVector< QDateTime > fluidicsPackChanges() const
QVector< QDateTime > sensorChanges() const
Qt::Alignment fluidicsPackChangedSymbolPosition() const
void paint(PaintContext *paintContext) override
virtual void drawDataPointSymbol(PaintContext *paintContext, const QPointF &pos, bool ok)
virtual void drawFluidicsPackChangedSymbol(PaintContext *paintContext, const QPointF &pos)
LineDiagram * clone() const override
void setSymbol(Symbol symbol, const QString &filename)
void setSensorChangedSymbolPosition(Qt::Alignment pos)
QPair< QDateTime, QDateTime > timeRange() const
QSvgRenderer * iconRenderer(Symbol symbol)
void setTimeRange(const QPair< QDateTime, QDateTime > &timeRange)
bool compare(const LeveyJenningsDiagram *other) const
const QPair< QPointF, QPointF > calculateDataBoundaries() const override
void setSensorChanges(const QVector< QDateTime > &changes)
virtual void drawSensorChangedSymbol(PaintContext *paintContext, const QPointF &pos)
virtual void drawLotChangeSymbol(PaintContext *paintContext, const QPointF &pos)
LineDiagram defines a common line diagram.
Measure is used to specify relative and absolute sizes in KDChart, e.g. font sizes.
Stores information about painting diagrams.
void setCoordinatePlane(AbstractCoordinatePlane *plane)
AbstractCoordinatePlane * coordinatePlane() const
QPainter * painter() const
A set of text attributes.
void setFontSize(const Measure &measure)
const QFont calculatedFont(const QObject *autoReferenceArea, KDChartEnums::MeasureOrientation autoReferenceOrientation) const
Returns the font in the size that is used at drawing time.

© 2001 Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
https://www.kdab.com/development-resources/qt-tools/kd-chart/
Generated by doxygen 1.9.1