KD Chart API Documentation 3.1
Loading...
Searching...
No Matches
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
21using namespace KDChart;
22
23qreal fastPow10(int x);
24
25DataDimensionsList 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
91DataDimension 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
132static 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
168void 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 *>(
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
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
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
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)
qreal fastPow10(int x)
@ GranularitySequence_125_25
@ GranularitySequence_25_50
@ GranularitySequenceIrregular
@ GranularitySequence_10_20
@ GranularitySequence_10_50
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
const T & at(int i) const const
int count(const T &value) const const
T & first()
void drawLine(const QLineF &line)
void fillRect(const QRectF &rectangle, const QBrush &brush)
void setPen(const QColor &color)
qreal x() const const
qreal y() const const
Horizontal

© 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 on Wed May 1 2024 00:01:10 for KD Chart API Documentation by doxygen 1.9.8