KChart

KChartLeveyJenningsGrid.cpp
1/*
2 * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
3 *
4 * This file is part of the KD Chart library.
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include "KChartLeveyJenningsGrid.h"
10
11#include "KChartLeveyJenningsDiagram.h"
12#include "KChartPaintContext.h"
13#include "KChartPainterSaver_p.h"
14#include "KChartPrintingParameters.h"
15#include "KChartMath_p.h"
16
17#include <QPainter>
18
19
20using namespace KChart;
21
22qreal fastPow10( int x );
23
24DataDimensionsList LeveyJenningsGrid::calculateGrid( const DataDimensionsList& rawDataDimensions ) const
25{
26 Q_ASSERT_X ( rawDataDimensions.count() == 2, "CartesianGrid::calculateGrid",
27 "Error: calculateGrid() expects a list with exactly two entries." );
28
29 LeveyJenningsCoordinatePlane* plane = dynamic_cast< LeveyJenningsCoordinatePlane*>( mPlane );
30 Q_ASSERT_X ( plane, "LeveyJenningsGrid::calculateGrid",
31 "Error: PaintContext::calculatePlane() called, but no cartesian plane set." );
32
33 DataDimensionsList l( rawDataDimensions );
34 // rule: Returned list is either empty, or it is providing two
35 // valid dimensions, complete with two non-Zero step widths.
36 if ( isBoundariesValid( l ) ) {
37 const QPointF translatedBottomLeft( plane->translateBack( plane->geometry().bottomLeft() ) );
38 const QPointF translatedTopRight( plane->translateBack( plane->geometry().topRight() ) );
39
40 if ( l.first().isCalculated
41 && plane->autoAdjustGridToZoom()
42 && plane->axesCalcModeX() == CartesianCoordinatePlane::Linear
43 && plane->zoomFactorX() > 1.0 )
44 {
45 l.first().start = translatedBottomLeft.x();
46 l.first().end = translatedTopRight.x();
47 }
48
49 const DataDimension dimX
50 = calculateGridXY( l.first(), Qt::Horizontal, false, false );
51 if ( dimX.stepWidth ) {
52 // one time for the min/max value
53 const DataDimension minMaxY
54 = calculateGridXY( l.last(), Qt::Vertical, false, false );
55
56 if ( plane->autoAdjustGridToZoom()
57 && plane->axesCalcModeY() == CartesianCoordinatePlane::Linear
58 && plane->zoomFactorY() > 1.0 )
59 {
60 l.last().start = translatedBottomLeft.y();
61 l.last().end = translatedTopRight.y();
62 }
63 // and one other time for the step width
64 const DataDimension dimY
65 = calculateGridXY( l.last(), Qt::Vertical, false, false );
66 if ( dimY.stepWidth ) {
67 l.first().start = dimX.start;
68 l.first().end = dimX.end;
69 l.first().stepWidth = dimX.stepWidth;
70 l.first().subStepWidth = dimX.subStepWidth;
71 l.last().start = minMaxY.start;
72 l.last().end = minMaxY.end;
73 l.last().stepWidth = dimY.stepWidth;
74 //qDebug() << "CartesianGrid::calculateGrid() final grid y-range:" << l.last().end - l.last().start << " step width:" << l.last().stepWidth << endl;
75 // calculate some reasonable subSteps if the
76 // user did not set the sub grid but did set
77 // the stepWidth.
78 if ( dimY.subStepWidth == 0 )
79 l.last().subStepWidth = dimY.stepWidth/2;
80 else
81 l.last().subStepWidth = dimY.subStepWidth;
82 }
83 }
84 }
85 //qDebug() << "CartesianGrid::calculateGrid() final grid Y-range:" << l.last().end - l.last().start << " step width:" << l.last().stepWidth;
86 //qDebug() << "CartesianGrid::calculateGrid() final grid X-range:" << l.first().end - l.first().start << " step width:" << l.first().stepWidth;
87
88 return l;
89}
90
91#if defined ( Q_WS_WIN)
92#define trunc(x) ((int)(x))
93#endif
94
95DataDimension LeveyJenningsGrid::calculateGridXY(
96 const DataDimension& rawDataDimension,
97 Qt::Orientation orientation,
98 bool adjustLower, bool adjustUpper ) const
99{
100 DataDimension dim( rawDataDimension );
101 if ( dim.isCalculated && dim.start != dim.end ) {
102 // linear ( == not-logarithmic) calculation
103 if ( dim.stepWidth == 0.0 ) {
104 QList<qreal> granularities;
105 switch ( dim.sequence ) {
106 case KChartEnums::GranularitySequence_10_20:
107 granularities << 1.0 << 2.0;
108 break;
109 case KChartEnums::GranularitySequence_10_50:
110 granularities << 1.0 << 5.0;
111 break;
112 case KChartEnums::GranularitySequence_25_50:
113 granularities << 2.5 << 5.0;
114 break;
115 case KChartEnums::GranularitySequence_125_25:
116 granularities << 1.25 << 2.5;
117 break;
118 case KChartEnums::GranularitySequenceIrregular:
119 granularities << 1.0 << 1.25 << 2.0 << 2.5 << 5.0;
120 break;
121 }
122 //qDebug("CartesianGrid::calculateGridXY() dim.start: %f dim.end: %f", dim.start, dim.end);
123 calculateStepWidth(
124 dim.start, dim.end, granularities, orientation,
125 dim.stepWidth, dim.subStepWidth,
126 adjustLower, adjustUpper );
127 }
128 } else {
129 //qDebug() << "CartesianGrid::calculateGridXY() returns stepWidth 1.0 !!";
130 // Do not ignore the user configuration
131 dim.stepWidth = dim.stepWidth ? dim.stepWidth : 1.0;
132 }
133 return dim;
134}
135
136static void calculateSteps(
137 qreal start_, qreal end_, const QList<qreal>& list,
138 int minSteps, int maxSteps,
139 int power,
140 qreal& steps, qreal& stepWidth,
141 bool adjustLower, bool adjustUpper )
142{
143 //qDebug("-----------------------------------\nstart: %f end: %f power-of-ten: %i", start_, end_, power);
144
145 qreal distance = 0.0;
146 steps = 0.0;
147
148 const int lastIdx = list.count()-1;
149 for ( int i = 0; i <= lastIdx; ++i ) {
150 const qreal testStepWidth = list.at(lastIdx - i) * fastPow10( power );
151 //qDebug( "testing step width: %f", testStepWidth);
152 qreal start = qMin( start_, end_ );
153 qreal end = qMax( start_, end_ );
154 //qDebug("pre adjusting start: %f end: %f", start, end);
155 AbstractGrid::adjustLowerUpperRange( start, end, testStepWidth, adjustLower, adjustUpper );
156 //qDebug("post adjusting start: %f end: %f", start, end);
157
158 const qreal testDistance = qAbs(end - start);
159 const qreal testSteps = testDistance / testStepWidth;
160
161 //qDebug() << "testDistance:" << testDistance << " distance:" << distance;
162 if ( (minSteps <= testSteps) && (testSteps <= maxSteps)
163 && ( (steps == 0.0) || (testDistance <= distance) ) ) {
164 steps = testSteps;
165 stepWidth = testStepWidth;
166 distance = testDistance;
167 //qDebug( "start: %f end: %f step width: %f steps: %f distance: %f", start, end, stepWidth, steps, distance);
168 }
169 }
170}
171
172void LeveyJenningsGrid::calculateStepWidth(
173 qreal start_, qreal end_,
174 const QList<qreal>& granularities,
175 Qt::Orientation orientation,
176 qreal& stepWidth, qreal& subStepWidth,
177 bool adjustLower, bool adjustUpper ) const
178{
179 Q_UNUSED( orientation );
180
181 Q_ASSERT_X ( granularities.count(), "CartesianGrid::calculateStepWidth",
182 "Error: The list of GranularitySequence values is empty." );
183 QList<qreal> list( granularities );
184 std::sort(list.begin(), list.end());
185
186 const qreal start = qMin( start_, end_);
187 const qreal end = qMax( start_, end_);
188 const qreal distance = end - start;
189 //qDebug( "raw data start: %f end: %f", start, end);
190
191 //FIXME(khz): make minSteps and maxSteps configurable by the user.
192 const int minSteps = 2;
193 const int maxSteps = 12;
194
195 qreal steps;
196 int power = 0;
197 while ( list.last() * fastPow10( power ) < distance ) {
198 ++power;
199 };
200 // We have the sequence *two* times in the calculation test list,
201 // so we will be sure to find the best match:
202 const int count = list.count();
203 QList<qreal> testList;
204 for ( int i = 0; i < count; ++i )
205 testList << list.at(i) * 0.1;
206 testList << list;
207 do{
208 //qDebug() << "list:" << testList;
209 //qDebug( "calculating steps: power: %i", power);
210 calculateSteps( start, end, testList, minSteps, maxSteps, power,
211 steps, stepWidth,
212 adjustLower, adjustUpper );
213 --power;
214 }while ( steps == 0.0 );
215 ++power;
216 //qDebug( "steps calculated: stepWidth: %f steps: %f", stepWidth, steps);
217
218 // find the matching sub-grid line width in case it is
219 // not set by the user
220
221 if ( subStepWidth == 0.0 ) {
222 if ( stepWidth == list.first() * fastPow10( power ) ) {
223 subStepWidth = list.last() * fastPow10( power-1 );
224 //qDebug("A");
225 } else if ( stepWidth == list.first() * fastPow10( power-1 ) ) {
226 subStepWidth = list.last() * fastPow10( power-2 );
227 //qDebug("B");
228 } else {
229 qreal smallerStepWidth = list.first();
230 for ( int i = 1; i < list.count(); ++i ) {
231 if ( stepWidth == list.at( i ) * fastPow10( power ) ) {
232 subStepWidth = smallerStepWidth * fastPow10( power );
233 break;
234 }
235 if ( stepWidth == list.at( i ) * fastPow10( power-1 ) ) {
236 subStepWidth = smallerStepWidth * fastPow10( power-1 );
237 break;
238 }
239 smallerStepWidth = list.at( i );
240 }
241
242 //qDebug("C");
243 }
244 }
245 //qDebug("LeveyJenningsGrid::calculateStepWidth() found stepWidth %f (%f steps) and sub-stepWidth %f", stepWidth, steps, subStepWidth);
246}
247
249{
250 // This plane is used for translating the coordinates - not for the data boundaries
251 PainterSaver p( context->painter() );
252 LeveyJenningsCoordinatePlane* plane = qobject_cast< LeveyJenningsCoordinatePlane* >(
253 mPlane->sharedAxisMasterPlane( context->painter() ) );
254 Q_ASSERT_X ( plane, "LeveyJenningsGrid::drawGrid",
255 "Bad function call: PaintContext::coodinatePlane() NOT a Levey Jennings plane." );
256
257 LeveyJenningsDiagram* diag = qobject_cast< LeveyJenningsDiagram* >( plane->diagram() );
258 if ( !diag ) {
259 return;
260 }
261
262 const LeveyJenningsGridAttributes gridAttrs( plane->gridAttributes() );
263
264 // update the calculated mDataDimensions before using them
265 updateData( context->coordinatePlane() );
266
267 // test for programming errors: critical
268 Q_ASSERT_X ( mDataDimensions.count() == 2, "CartesianGrid::drawGrid",
269 "Error: updateData did not return exactly two dimensions." );
270
271 // test for invalid boundaries: non-critical
272 if ( !isBoundariesValid( mDataDimensions ) ) {
273 return;
274 }
275 //qDebug() << "B";
276
277 DataDimension dimX = mDataDimensions.first();
278 // this happens if there's only one data point
279 if ( dimX.start == 0.0 && dimX.end == 0.0 )
280 dimX.end += plane->geometry().width();
281
282 // first we draw the expected lines
283 // draw the "mean" line
284 const float meanValue = diag->expectedMeanValue();
285 const float standardDeviation = diag->expectedStandardDeviation();
286
287 // then the calculated ones
288 const float calcMeanValue = diag->calculatedMeanValue();
289 const float calcStandardDeviation = diag->calculatedStandardDeviation();
290
291
292 // draw the normal range
293 QPointF topLeft = plane->translate( QPointF( dimX.start, meanValue - 2 * standardDeviation ) );
294 QPointF bottomRight = plane->translate( QPointF( dimX.end, meanValue + 2 * standardDeviation ) );
295 context->painter()->fillRect( QRectF( topLeft, QSizeF( bottomRight.x() - topLeft.x(), bottomRight.y() - topLeft.y() ) ),
296 gridAttrs.rangeBrush( LeveyJenningsGridAttributes::NormalRange ) );
297
298 // draw the critical range
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() ) ),
302 gridAttrs.rangeBrush( LeveyJenningsGridAttributes::CriticalRange ) );
303
304 topLeft = plane->translate( QPointF( dimX.start, meanValue - 2 * standardDeviation ) );
305 bottomRight = plane->translate( QPointF( dimX.end, meanValue - 3 * standardDeviation ) );
306 context->painter()->fillRect( QRectF( topLeft, QSizeF( bottomRight.x() - topLeft.x(), bottomRight.y() - topLeft.y() ) ),
307 gridAttrs.rangeBrush( LeveyJenningsGridAttributes::CriticalRange ) );
308
309 // draw the "out of range" range
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() ) ),
313 gridAttrs.rangeBrush( LeveyJenningsGridAttributes::OutOfRange ) );
314
315 topLeft = plane->translate( QPointF( dimX.start, meanValue - 3 * standardDeviation ) );
316 bottomRight = plane->translate( QPointF( dimX.end, meanValue - 4 * standardDeviation ) );
317 context->painter()->fillRect( QRectF( topLeft, QSizeF( bottomRight.x() - topLeft.x(), bottomRight.y() - topLeft.y() ) ),
318 gridAttrs.rangeBrush( LeveyJenningsGridAttributes::OutOfRange ) );
319
320 // the "expected" grid
321 if ( gridAttrs.isGridVisible( LeveyJenningsGridAttributes::Expected ) )
322 {
323 context->painter()->setPen( gridAttrs.gridPen( LeveyJenningsGridAttributes::Expected ) );
324 context->painter()->drawLine( plane->translate( QPointF( dimX.start, meanValue ) ),
325 plane->translate( QPointF( dimX.end, meanValue ) ) );
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 context->painter()->drawLine( plane->translate( QPointF( dimX.start, meanValue - 2 * standardDeviation ) ),
333 plane->translate( QPointF( dimX.end, meanValue - 2 * standardDeviation ) ) );
334 context->painter()->drawLine( plane->translate( QPointF( dimX.start, meanValue - 3 * standardDeviation ) ),
335 plane->translate( QPointF( dimX.end, meanValue - 3 * standardDeviation ) ) );
336 context->painter()->drawLine( plane->translate( QPointF( dimX.start, meanValue - 4 * standardDeviation ) ),
337 plane->translate( QPointF( dimX.end, meanValue - 4 * standardDeviation ) ) );
338 }
339
340 // the "calculated" grid
341 if ( gridAttrs.isGridVisible( LeveyJenningsGridAttributes::Calculated ) )
342 {
343 context->painter()->setPen( gridAttrs.gridPen( LeveyJenningsGridAttributes::Calculated ) );
344 context->painter()->drawLine( plane->translate( QPointF( dimX.start, calcMeanValue ) ),
345 plane->translate( QPointF( dimX.end, calcMeanValue ) ) );
346 context->painter()->drawLine( plane->translate( QPointF( dimX.start, calcMeanValue + 2 * calcStandardDeviation ) ),
347 plane->translate( QPointF( dimX.end, calcMeanValue + 2 * calcStandardDeviation ) ) );
348 context->painter()->drawLine( plane->translate( QPointF( dimX.start, calcMeanValue + 3 * calcStandardDeviation ) ),
349 plane->translate( QPointF( dimX.end, calcMeanValue + 3 * calcStandardDeviation ) ) );
350 context->painter()->drawLine( plane->translate( QPointF( dimX.start, calcMeanValue - 2 * calcStandardDeviation ) ),
351 plane->translate( QPointF( dimX.end, calcMeanValue - 2 * calcStandardDeviation ) ) );
352 context->painter()->drawLine( plane->translate( QPointF( dimX.start, calcMeanValue - 3 * calcStandardDeviation ) ),
353 plane->translate( QPointF( dimX.end, calcMeanValue - 3 * calcStandardDeviation ) ) );
354 }
355}
QRect geometry() const override
pure virtual in QLayoutItem
DataDimensionsList updateData(AbstractCoordinatePlane *plane)
Returns the cached result of data calculation.
static void adjustLowerUpperRange(qreal &start, qreal &end, qreal stepWidth, bool adjustLower, bool adjustUpper)
Adjusts start and/or end so that they are a multiple of stepWidth.
static bool isBoundariesValid(const QRectF &r)
Checks whether both coordinates of r are valid according to isValueValid.
const QPointF translate(const QPointF &diagramPoint) const override
Translate the given point in value space coordinates to a position in pixel space.
bool autoAdjustGridToZoom() const
Return the status of the built-in grid adjusting feature.
Helper class for one dimension of data, e.g.
Levey Jennings coordinate plane This is actually nothing real more than a plain cartesian coordinate ...
LeveyDiagram defines a Levey Jennings chart.
float expectedStandardDeviation() const
Returns the expected standard deviation over all QC values.
float calculatedStandardDeviation() const
Returns the calculated standard deviation over all QC values.
float expectedMeanValue() const
Returns the expected mean values over all QC values.
float calculatedMeanValue() const
Returns the calculated mean values over all QC values.
A set of attributes controlling the appearance of grids.
void drawGrid(PaintContext *context) override
Doing the actual drawing.
Stores information about painting diagrams.
Q_SCRIPTABLE Q_NOREPLY void start()
KIOCORE_EXPORT QStringList list(const QString &fileClass)
const QList< QKeySequence > & end()
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
const_reference at(qsizetype i) const const
iterator begin()
qsizetype count() const const
iterator end()
T & first()
T & last()
void drawLine(const QLine &line)
void fillRect(const QRect &rectangle, QGradient::Preset preset)
void setPen(Qt::PenStyle style)
qreal x() const const
qreal y() const const
QPoint bottomLeft() const const
QPoint topRight() const const
int width() const const
Horizontal
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:24 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.