KChart

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

KDE's Doxygen guidelines are available online.