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 
20 using namespace KChart;
21 
22 qreal fastPow10( int x );
23 
24 DataDimensionsList 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 
95 DataDimension 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 
136 static 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 
172 void 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 }
T & first()
DataDimensionsList updateData(AbstractCoordinatePlane *plane)
Returns the cached result of data calculation.
void setPen(const QColor &color)
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.
float calculatedStandardDeviation() const
Returns the calculated standard deviation over all QC values.
int count(const T &value) const const
Q_SCRIPTABLE Q_NOREPLY void start()
int width() const const
float expectedMeanValue() const
Returns the expected mean values over all QC values.
A set of attributes controlling the appearance of grids.
Stores information about painting diagrams.
void drawGrid(PaintContext *context) override
Doing the actual drawing.
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
Levey Jennings coordinate plane This is actually nothing real more than a plain cartesian coordinate ...
const QPointF translate(const QPointF &diagramPoint) const override
Translate the given point in value space coordinates to a position in pixel space.
void fillRect(const QRectF &rectangle, const QBrush &brush)
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
float calculatedMeanValue() const
Returns the calculated mean values over all QC values.
LeveyDiagram defines a Levey Jennings chart.
Horizontal
const T & at(int i) const const
QRect geometry() const override
pure virtual in QLayoutItem
QPoint bottomLeft() const const
T & last()
static bool isBoundariesValid(const QRectF &r)
Checks whether both coordinates of r are valid according to isValueValid.
qreal x() const const
qreal y() const const
QPoint topRight() const const
void drawLine(const QLineF &line)
QList::iterator begin()
float expectedStandardDeviation() const
Returns the expected standard deviation over all QC values.
bool autoAdjustGridToZoom() const
Return the status of the built-in grid adjusting feature.
QList::iterator end()
Helper class for one dimension of data, e.g.
const QList< QKeySequence > & end()
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Oct 6 2022 03:55:07 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.