KD Chart API Documentation  3.1
KDChartLeveyJenningsGrid.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 
13 #include "KDChartPaintContext.h"
14 #include "KDChartPainterSaver_p.h"
16 
17 #include <QPainter>
18 
19 #include <KDABLibFakes>
20 
21 using namespace KDChart;
22 
23 qreal fastPow10(int x);
24 
25 DataDimensionsList LeveyJenningsGrid::calculateGrid(const DataDimensionsList &rawDataDimensions) const
26 {
27  Q_ASSERT_X(rawDataDimensions.count() == 2, "CartesianGrid::calculateGrid",
28  "Error: calculateGrid() expects a list with exactly two entries.");
29 
30  auto *plane = dynamic_cast<LeveyJenningsCoordinatePlane *>(mPlane);
31  Q_ASSERT_X(plane, "LeveyJenningsGrid::calculateGrid",
32  "Error: PaintContext::calculatePlane() called, but no cartesian plane set.");
33 
34  DataDimensionsList l(rawDataDimensions);
35  // rule: Returned list is either empty, or it is providing two
36  // valid dimensions, complete with two non-Zero step widths.
37  if (isBoundariesValid(l)) {
38  const QPointF translatedBottomLeft(plane->translateBack(plane->geometry().bottomLeft()));
39  const QPointF translatedTopRight(plane->translateBack(plane->geometry().topRight()));
40 
41  if (l.first().isCalculated
42  && plane->autoAdjustGridToZoom()
43  && plane->axesCalcModeX() == CartesianCoordinatePlane::Linear
44  && plane->zoomFactorX() > 1.0) {
45  l.first().start = translatedBottomLeft.x();
46  l.first().end = translatedTopRight.x();
47  }
48 
49  const DataDimension dimX = calculateGridXY(l.first(), Qt::Horizontal, false, false);
50  if (dimX.stepWidth) {
51  // one time for the min/max value
52  const DataDimension minMaxY = calculateGridXY(l.last(), Qt::Vertical, false, false);
53 
54  if (plane->autoAdjustGridToZoom()
55  && plane->axesCalcModeY() == CartesianCoordinatePlane::Linear
56  && plane->zoomFactorY() > 1.0) {
57  l.last().start = translatedBottomLeft.y();
58  l.last().end = translatedTopRight.y();
59  }
60  // and one other time for the step width
61  const DataDimension dimY = calculateGridXY(l.last(), Qt::Vertical, false, false);
62  if (dimY.stepWidth) {
63  l.first().start = dimX.start;
64  l.first().end = dimX.end;
65  l.first().stepWidth = dimX.stepWidth;
66  l.first().subStepWidth = dimX.subStepWidth;
67  l.last().start = minMaxY.start;
68  l.last().end = minMaxY.end;
69  l.last().stepWidth = dimY.stepWidth;
70  // qDebug() << "CartesianGrid::calculateGrid() final grid y-range:" << l.last().end - l.last().start << " step width:" << l.last().stepWidth << endl;
71  // calculate some reasonable subSteps if the
72  // user did not set the sub grid but did set
73  // the stepWidth.
74  if (dimY.subStepWidth == 0)
75  l.last().subStepWidth = dimY.stepWidth / 2;
76  else
77  l.last().subStepWidth = dimY.subStepWidth;
78  }
79  }
80  }
81  // qDebug() << "CartesianGrid::calculateGrid() final grid Y-range:" << l.last().end - l.last().start << " step width:" << l.last().stepWidth;
82  // qDebug() << "CartesianGrid::calculateGrid() final grid X-range:" << l.first().end - l.first().start << " step width:" << l.first().stepWidth;
83 
84  return l;
85 }
86 
87 #if defined(Q_WS_WIN)
88 #define trunc(x) (( int )(x))
89 #endif
90 
91 DataDimension LeveyJenningsGrid::calculateGridXY(
92  const DataDimension &rawDataDimension,
93  Qt::Orientation orientation,
94  bool adjustLower, bool adjustUpper) const
95 {
96  DataDimension dim(rawDataDimension);
97  if (dim.isCalculated && dim.start != dim.end) {
98  // linear ( == not-logarithmic) calculation
99  if (dim.stepWidth == 0.0) {
100  QList<qreal> granularities;
101  switch (dim.sequence) {
103  granularities << 1.0 << 2.0;
104  break;
106  granularities << 1.0 << 5.0;
107  break;
109  granularities << 2.5 << 5.0;
110  break;
112  granularities << 1.25 << 2.5;
113  break;
115  granularities << 1.0 << 1.25 << 2.0 << 2.5 << 5.0;
116  break;
117  }
118  // qDebug("CartesianGrid::calculateGridXY() dim.start: %f dim.end: %f", dim.start, dim.end);
119  calculateStepWidth(
120  dim.start, dim.end, granularities, orientation,
121  dim.stepWidth, dim.subStepWidth,
122  adjustLower, adjustUpper);
123  }
124  } else {
125  // qDebug() << "CartesianGrid::calculateGridXY() returns stepWidth 1.0 !!";
126  // Do not ignore the user configuration
127  dim.stepWidth = dim.stepWidth ? dim.stepWidth : 1.0;
128  }
129  return dim;
130 }
131 
132 static void calculateSteps(
133  qreal start_, qreal end_, const QList<qreal> &list,
134  int minSteps, int maxSteps,
135  int power,
136  qreal &steps, qreal &stepWidth,
137  bool adjustLower, bool adjustUpper)
138 {
139  // qDebug("-----------------------------------\nstart: %f end: %f power-of-ten: %i", start_, end_, power);
140 
141  qreal distance = 0.0;
142  steps = 0.0;
143 
144  const int lastIdx = list.count() - 1;
145  for (int i = 0; i <= lastIdx; ++i) {
146  const qreal testStepWidth = list.at(lastIdx - i) * fastPow10(power);
147  // qDebug( "testing step width: %f", testStepWidth);
148  qreal start = qMin(start_, end_);
149  qreal end = qMax(start_, end_);
150  // qDebug("pre adjusting start: %f end: %f", start, end);
151  AbstractGrid::adjustLowerUpperRange(start, end, testStepWidth, adjustLower, adjustUpper);
152  // qDebug("post adjusting start: %f end: %f", start, end);
153 
154  const qreal testDistance = qAbs(end - start);
155  const qreal testSteps = testDistance / testStepWidth;
156 
157  // qDebug() << "testDistance:" << testDistance << " distance:" << distance;
158  if ((minSteps <= testSteps) && (testSteps <= maxSteps)
159  && ((steps == 0.0) || (testDistance <= distance))) {
160  steps = testSteps;
161  stepWidth = testStepWidth;
162  distance = testDistance;
163  // qDebug( "start: %f end: %f step width: %f steps: %f distance: %f", start, end, stepWidth, steps, distance);
164  }
165  }
166 }
167 
168 void LeveyJenningsGrid::calculateStepWidth(
169  qreal start_, qreal end_,
170  const QList<qreal> &granularities,
171  Qt::Orientation orientation,
172  qreal &stepWidth, qreal &subStepWidth,
173  bool adjustLower, bool adjustUpper) const
174 {
175  Q_UNUSED(orientation);
176 
177  Q_ASSERT_X(granularities.count(), "CartesianGrid::calculateStepWidth",
178  "Error: The list of GranularitySequence values is empty.");
179  QList<qreal> list(granularities);
180  std::sort(list.begin(), list.end());
181 
182  const qreal start = qMin(start_, end_);
183  const qreal end = qMax(start_, end_);
184  const qreal distance = end - start;
185  // qDebug( "raw data start: %f end: %f", start, end);
186 
187  // FIXME(khz): make minSteps and maxSteps configurable by the user.
188  const int minSteps = 2;
189  const int maxSteps = 12;
190 
191  qreal steps;
192  int power = 0;
193  while (list.last() * fastPow10(power) < distance) {
194  ++power;
195  };
196  // We have the sequence *two* times in the calculation test list,
197  // so we will be sure to find the best match:
198  const int count = list.count();
199  QList<qreal> testList;
200  for (int i = 0; i < count; ++i)
201  testList << list.at(i) * 0.1;
202  testList << list;
203  do {
204  // qDebug() << "list:" << testList;
205  // qDebug( "calculating steps: power: %i", power);
206  calculateSteps(start, end, testList, minSteps, maxSteps, power,
207  steps, stepWidth,
208  adjustLower, adjustUpper);
209  --power;
210  } while (steps == 0.0);
211  ++power;
212  // qDebug( "steps calculated: stepWidth: %f steps: %f", stepWidth, steps);
213 
214  // find the matching sub-grid line width in case it is
215  // not set by the user
216 
217  if (subStepWidth == 0.0) {
218  if (stepWidth == list.first() * fastPow10(power)) {
219  subStepWidth = list.last() * fastPow10(power - 1);
220  // qDebug("A");
221  } else if (stepWidth == list.first() * fastPow10(power - 1)) {
222  subStepWidth = list.last() * fastPow10(power - 2);
223  // qDebug("B");
224  } else {
225  qreal smallerStepWidth = list.first();
226  for (int i = 1; i < list.count(); ++i) {
227  if (stepWidth == list.at(i) * fastPow10(power)) {
228  subStepWidth = smallerStepWidth * fastPow10(power);
229  break;
230  }
231  if (stepWidth == list.at(i) * fastPow10(power - 1)) {
232  subStepWidth = smallerStepWidth * fastPow10(power - 1);
233  break;
234  }
235  smallerStepWidth = list.at(i);
236  }
237 
238  // qDebug("C");
239  }
240  }
241  // qDebug("LeveyJenningsGrid::calculateStepWidth() found stepWidth %f (%f steps) and sub-stepWidth %f", stepWidth, steps, subStepWidth);
242 }
243 
245 {
246  // This plane is used for translating the coordinates - not for the data boundaries
247  PainterSaver p(context->painter());
248  auto *plane = qobject_cast<LeveyJenningsCoordinatePlane *>(
249  mPlane->sharedAxisMasterPlane(context->painter()));
250  Q_ASSERT_X(plane, "LeveyJenningsGrid::drawGrid",
251  "Bad function call: PaintContext::coodinatePlane() NOT a Levey Jennings plane.");
252 
253  auto *diag = qobject_cast<LeveyJenningsDiagram *>(plane->diagram());
254  if (!diag) {
255  return;
256  }
257 
258  const LeveyJenningsGridAttributes gridAttrs(plane->gridAttributes());
259 
260  // update the calculated mDataDimensions before using them
261  updateData(context->coordinatePlane());
262 
263  // test for programming errors: critical
264  Q_ASSERT_X(mDataDimensions.count() == 2, "CartesianGrid::drawGrid",
265  "Error: updateData did not return exactly two dimensions.");
266 
267  // test for invalid boundaries: non-critical
269  return;
270  }
271  // qDebug() << "B";
272 
273  DataDimension dimX = mDataDimensions.first();
274  // this happens if there's only one data point
275  if (dimX.start == 0.0 && dimX.end == 0.0)
276  dimX.end += plane->geometry().width();
277 
278  // first we draw the expected lines
279  // draw the "mean" line
280  const float meanValue = diag->expectedMeanValue();
281  const float standardDeviation = diag->expectedStandardDeviation();
282 
283  // then the calculated ones
284  const float calcMeanValue = diag->calculatedMeanValue();
285  const float calcStandardDeviation = diag->calculatedStandardDeviation();
286 
287  // draw the normal range
288  QPointF topLeft = plane->translate(QPointF(dimX.start, meanValue - 2 * standardDeviation));
289  QPointF bottomRight = plane->translate(QPointF(dimX.end, meanValue + 2 * standardDeviation));
290  context->painter()->fillRect(QRectF(topLeft, QSizeF(bottomRight.x() - topLeft.x(), bottomRight.y() - topLeft.y())),
292 
293  // draw the critical range
294  topLeft = plane->translate(QPointF(dimX.start, meanValue + 2 * standardDeviation));
295  bottomRight = plane->translate(QPointF(dimX.end, meanValue + 3 * standardDeviation));
296  context->painter()->fillRect(QRectF(topLeft, QSizeF(bottomRight.x() - topLeft.x(), bottomRight.y() - topLeft.y())),
298 
299  topLeft = plane->translate(QPointF(dimX.start, meanValue - 2 * standardDeviation));
300  bottomRight = plane->translate(QPointF(dimX.end, meanValue - 3 * standardDeviation));
301  context->painter()->fillRect(QRectF(topLeft, QSizeF(bottomRight.x() - topLeft.x(), bottomRight.y() - topLeft.y())),
303 
304  // draw the "out of range" range
305  topLeft = plane->translate(QPointF(dimX.start, meanValue + 3 * standardDeviation));
306  bottomRight = plane->translate(QPointF(dimX.end, meanValue + 4 * standardDeviation));
307  context->painter()->fillRect(QRectF(topLeft, QSizeF(bottomRight.x() - topLeft.x(), bottomRight.y() - topLeft.y())),
309 
310  topLeft = plane->translate(QPointF(dimX.start, meanValue - 3 * standardDeviation));
311  bottomRight = plane->translate(QPointF(dimX.end, meanValue - 4 * standardDeviation));
312  context->painter()->fillRect(QRectF(topLeft, QSizeF(bottomRight.x() - topLeft.x(), bottomRight.y() - topLeft.y())),
314 
315  // the "expected" grid
317  context->painter()->setPen(gridAttrs.gridPen(LeveyJenningsGridAttributes::Expected));
318  context->painter()->drawLine(plane->translate(QPointF(dimX.start, meanValue)),
319  plane->translate(QPointF(dimX.end, meanValue)));
320  context->painter()->drawLine(plane->translate(QPointF(dimX.start, meanValue + 2 * standardDeviation)),
321  plane->translate(QPointF(dimX.end, meanValue + 2 * standardDeviation)));
322  context->painter()->drawLine(plane->translate(QPointF(dimX.start, meanValue + 3 * standardDeviation)),
323  plane->translate(QPointF(dimX.end, meanValue + 3 * standardDeviation)));
324  context->painter()->drawLine(plane->translate(QPointF(dimX.start, meanValue + 4 * standardDeviation)),
325  plane->translate(QPointF(dimX.end, meanValue + 4 * standardDeviation)));
326  context->painter()->drawLine(plane->translate(QPointF(dimX.start, meanValue - 2 * standardDeviation)),
327  plane->translate(QPointF(dimX.end, meanValue - 2 * standardDeviation)));
328  context->painter()->drawLine(plane->translate(QPointF(dimX.start, meanValue - 3 * standardDeviation)),
329  plane->translate(QPointF(dimX.end, meanValue - 3 * standardDeviation)));
330  context->painter()->drawLine(plane->translate(QPointF(dimX.start, meanValue - 4 * standardDeviation)),
331  plane->translate(QPointF(dimX.end, meanValue - 4 * standardDeviation)));
332  }
333 
334  // the "calculated" grid
336  context->painter()->setPen(gridAttrs.gridPen(LeveyJenningsGridAttributes::Calculated));
337  context->painter()->drawLine(plane->translate(QPointF(dimX.start, calcMeanValue)),
338  plane->translate(QPointF(dimX.end, calcMeanValue)));
339  context->painter()->drawLine(plane->translate(QPointF(dimX.start, calcMeanValue + 2 * calcStandardDeviation)),
340  plane->translate(QPointF(dimX.end, calcMeanValue + 2 * calcStandardDeviation)));
341  context->painter()->drawLine(plane->translate(QPointF(dimX.start, calcMeanValue + 3 * calcStandardDeviation)),
342  plane->translate(QPointF(dimX.end, calcMeanValue + 3 * calcStandardDeviation)));
343  context->painter()->drawLine(plane->translate(QPointF(dimX.start, calcMeanValue - 2 * calcStandardDeviation)),
344  plane->translate(QPointF(dimX.end, calcMeanValue - 2 * calcStandardDeviation)));
345  context->painter()->drawLine(plane->translate(QPointF(dimX.start, calcMeanValue - 3 * calcStandardDeviation)),
346  plane->translate(QPointF(dimX.end, calcMeanValue - 3 * calcStandardDeviation)));
347  }
348 }
static void calculateSteps(qreal start_, qreal end_, const QList< qreal > &list, int minSteps, int maxSteps, int power, qreal &steps, qreal &stepWidth, bool adjustLower, bool adjustUpper)
qreal fastPow10(int x)
@ GranularitySequence_125_25
Definition: KDChartEnums.h:88
@ GranularitySequence_25_50
Definition: KDChartEnums.h:87
@ GranularitySequenceIrregular
Definition: KDChartEnums.h:89
@ GranularitySequence_10_20
Definition: KDChartEnums.h:85
@ GranularitySequence_10_50
Definition: KDChartEnums.h:86
virtual AbstractCoordinatePlane * sharedAxisMasterPlane(QPainter *p=nullptr)
DataDimensionsList updateData(AbstractCoordinatePlane *plane)
Returns the cached result of data calculation.
DataDimensionsList mDataDimensions
AbstractCoordinatePlane * mPlane
static void adjustLowerUpperRange(qreal &start, qreal &end, qreal stepWidth, bool adjustLower, bool adjustUpper)
static bool isBoundariesValid(const QRectF &r)
Helper class for one dimension of data, e.g. for the rows in a data model, or for the labels of an ax...
Levey Jennings coordinate plane This is actually nothing real more than a plain cartesian coordinate ...
A set of attributes controlling the appearance of grids.
void drawGrid(PaintContext *context) override
Stores information about painting diagrams.
AbstractCoordinatePlane * coordinatePlane() const
QPainter * painter() const
QList< DataDimension > DataDimensionsList

© 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