KD Chart 2  [rev.2.7]
KDChartStackedLineDiagram_p.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 ** Copyright (C) 2001-2020 Klaralvdalens Datakonsult AB. All rights reserved.
3 **
4 ** This file is part of the KD Chart library.
5 **
6 ** Licensees holding valid commercial KD Chart licenses may use this file in
7 ** accordance with the KD Chart Commercial License Agreement provided with
8 ** the Software.
9 **
10 **
11 ** This file may be distributed and/or modified under the terms of the
12 ** GNU General Public License version 2 and version 3 as published by the
13 ** Free Software Foundation and appearing in the file LICENSE.GPL.txt included.
14 **
15 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
16 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
17 **
18 ** Contact info@kdab.com if any conditions of this licensing are not
19 ** clear to you.
20 **
21 **********************************************************************/
22 
23 #include "KDChartStackedLineDiagram_p.h"
24 
25 #include <QAbstractItemModel>
26 
27 #include "KDChartBarDiagram.h"
28 #include "KDChartLineDiagram.h"
29 #include "KDChartTextAttributes.h"
30 #include "KDChartAttributesModel.h"
32 #include "PaintingHelpers_p.h"
33 
34 using namespace KDChart;
35 using namespace std;
36 
37 StackedLineDiagram::StackedLineDiagram( LineDiagram* d )
38  : LineDiagramType( d )
39 {
40 }
41 
42 LineDiagram::LineType StackedLineDiagram::type() const
43 {
44  return LineDiagram::Stacked;
45 }
46 
47 const QPair<QPointF, QPointF> StackedLineDiagram::calculateDataBoundaries() const
48 {
49  const int rowCount = compressor().modelDataRows();
50  const int colCount = compressor().modelDataColumns();
51  const qreal xMin = 0;
52  qreal xMax = diagram()->model() ? diagram()->model()->rowCount( diagram()->rootIndex() ) : 0;
53  if ( !diagram()->centerDataPoints() && diagram()->model() )
54  xMax -= 1;
55  qreal yMin = 0, yMax = 0;
56 
57  bool bStarting = true;
58  for ( int row = 0; row < rowCount; ++row )
59  {
60  // calculate sum of values per column - Find out stacked Min/Max
61  qreal stackedValues = 0.0;
62  qreal negativeStackedValues = 0.0;
63  for ( int col = datasetDimension() - 1; col < colCount; col += datasetDimension() ) {
64  const CartesianDiagramDataCompressor::CachePosition position( row, col );
65  const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
66 
67  if ( ISNAN( point.value ) )
68  continue;
69 
70  if ( point.value >= 0.0 )
71  stackedValues += point.value;
72  else
73  negativeStackedValues += point.value;
74  }
75 
76  if ( bStarting ) {
77  yMin = stackedValues;
78  yMax = stackedValues;
79  bStarting = false;
80  } else {
81  // take in account all stacked values
82  yMin = qMin( qMin( yMin, negativeStackedValues ), stackedValues );
83  yMax = qMax( qMax( yMax, negativeStackedValues ), stackedValues );
84  }
85  }
86 
87  const QPointF bottomLeft( xMin, yMin );
88  const QPointF topRight( xMax, yMax );
89 
90  return QPair<QPointF, QPointF> ( bottomLeft, topRight );
91 }
92 
93 void StackedLineDiagram::paint( PaintContext* ctx )
94 {
95  if ( qFuzzyIsNull( m_private->tension ) ) {
96  paintWithLines( ctx );
97 
98  } else {
99  paintWithSplines( ctx, m_private->tension );
100  }
101 }
102 
103 void StackedLineDiagram::paintWithLines( PaintContext* ctx )
104 {
105  reverseMapper().clear();
106 
107  const int columnCount = compressor().modelDataColumns();
108  const int rowCount = compressor().modelDataRows();
109 
110 // FIXME integrate column index retrieval to compressor:
111 // int maxFound = 0;
112 // { // find the last column number that is not hidden
113 // for ( int iColumn = datasetDimension() - 1;
114 // iColumn < columnCount;
115 // iColumn += datasetDimension() )
116 // if ( ! diagram()->isHidden( iColumn ) )
117 // maxFound = iColumn;
118 // }
119  //maxFound = columnCount;
120  // ^^^ temp
121 
122  LabelPaintCache lpc;
123  LineAttributesInfoList lineList;
124 
125  QVector< qreal > percentSumValues;
126 
127  QList<QPointF> bottomPoints;
128  bool bFirstDataset = true;
129 
130  for ( int column = 0; column < columnCount; ++column )
131  {
132  CartesianDiagramDataCompressor::CachePosition previousCellPosition;
133 
134  //display area can be set by dataset ( == column) and/or by cell
135  LineAttributes laPreviousCell; // by default no area is drawn
136  QModelIndex indexPreviousCell;
137  QList<QPolygonF> areas;
138  QList<QPointF> points;
139 
140  for ( int row = 0; row < rowCount; ++row ) {
141  const CartesianDiagramDataCompressor::CachePosition position( row, column );
142  CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
143  const QModelIndex sourceIndex = attributesModel()->mapToSource( point.index );
144 
145  const LineAttributes laCell = diagram()->lineAttributes( sourceIndex );
146  const bool bDisplayCellArea = laCell.displayArea();
147 
149 
150  if ( ISNAN( point.value ) && policy == LineAttributes::MissingValuesShownAsZero )
151  point.value = 0.0;
152 
153  qreal stackedValues = 0, nextValues = 0, nextKey = 0;
154  for ( int column2 = column; column2 >= 0; --column2 )
155  {
156  const CartesianDiagramDataCompressor::CachePosition position( row, column2 );
157  const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
158  if ( !ISNAN( point.value ) )
159  {
160  stackedValues += point.value;
161  }
162  else if ( policy == LineAttributes::MissingValuesAreBridged )
163  {
164  const qreal interpolation = interpolateMissingValue( position );
165  if ( !ISNAN( interpolation ) )
166  stackedValues += interpolation;
167  }
168 
169  //qDebug() << valueForCell( iRow, iColumn2 );
170  if ( row + 1 < rowCount ) {
171  const CartesianDiagramDataCompressor::CachePosition position( row + 1, column2 );
172  const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
173  if ( !ISNAN( point.value ) )
174  {
175  nextValues += point.value;
176  }
177  else if ( policy == LineAttributes::MissingValuesAreBridged )
178  {
179  const qreal interpolation = interpolateMissingValue( position );
180  if ( !ISNAN( interpolation ) )
181  nextValues += interpolation;
182  }
183  nextKey = point.key;
184  }
185  }
186  //qDebug() << stackedValues << endl;
187  const QPointF nextPoint = ctx->coordinatePlane()->translate( QPointF( diagram()->centerDataPoints() ? point.key + 0.5 : point.key, stackedValues ) );
188  points << nextPoint;
189 
190  const QPointF ptNorthWest( nextPoint );
191  const QPointF ptSouthWest(
192  bDisplayCellArea
193  ? ( bFirstDataset
194  ? ctx->coordinatePlane()->translate( QPointF( diagram()->centerDataPoints() ? point.key + 0.5 : point.key, 0.0 ) )
195  : bottomPoints.at( row )
196  )
197  : nextPoint );
198  QPointF ptNorthEast;
199  QPointF ptSouthEast;
200 
201  if ( row + 1 < rowCount ) {
202  QPointF toPoint = ctx->coordinatePlane()->translate( QPointF( diagram()->centerDataPoints() ? nextKey + 0.5 : nextKey, nextValues ) );
203  lineList.append( LineAttributesInfo( sourceIndex, nextPoint, toPoint ) );
204  ptNorthEast = toPoint;
205  ptSouthEast =
206  bDisplayCellArea
207  ? ( bFirstDataset
208  ? ctx->coordinatePlane()->translate( QPointF( diagram()->centerDataPoints() ? nextKey + 0.5 : nextKey, 0.0 ) )
209  : bottomPoints.at( row + 1 )
210  )
211  : toPoint;
212  if ( areas.count() && laCell != laPreviousCell ) {
213  PaintingHelpers::paintAreas( m_private, ctx, indexPreviousCell, areas, laPreviousCell.transparency() );
214  areas.clear();
215  }
216  if ( bDisplayCellArea ) {
217  QPolygonF poly;
218  poly << ptNorthWest << ptNorthEast << ptSouthEast << ptSouthWest;
219  areas << poly;
220  laPreviousCell = laCell;
221  indexPreviousCell = sourceIndex;
222  } else {
223  //qDebug() << "no area shown for row"<<iRow<<" column"<<iColumn;
224  }
225  } else {
226  ptNorthEast = ptNorthWest;
227  ptSouthEast = ptSouthWest;
228  }
229 
230  const PositionPoints pts( ptNorthWest, ptNorthEast, ptSouthEast, ptSouthWest );
231  if ( !ISNAN( point.value ) )
232  m_private->addLabel( &lpc, sourceIndex, &position, pts, Position::NorthWest,
233  Position::NorthWest, point.value );
234  }
235  if ( areas.count() ) {
236  PaintingHelpers::paintAreas( m_private, ctx, indexPreviousCell, areas, laPreviousCell.transparency() );
237  areas.clear();
238  }
239  bottomPoints = points;
240  bFirstDataset = false;
241  }
242  PaintingHelpers::paintElements( m_private, ctx, lpc, lineList );
243 }
244 
245 void StackedLineDiagram::paintWithSplines( PaintContext* ctx, qreal tension )
246 {
247  reverseMapper().clear();
248 
249  const int columnCount = compressor().modelDataColumns();
250  const int rowCount = compressor().modelDataRows();
251 
252  Q_ASSERT(dynamic_cast<CartesianCoordinatePlane*>(ctx->coordinatePlane()));
253  const auto plane = static_cast<CartesianCoordinatePlane*>(ctx->coordinatePlane());
254  const auto mainSplineDirection = plane->isHorizontalRangeReversed() ? ReverseSplineDirection : NormalSplineDirection;
255  const auto reverseSplineDirection = plane->isHorizontalRangeReversed() ? NormalSplineDirection : ReverseSplineDirection;
256 
257 // FIXME integrate column index retrieval to compressor:
258 // int maxFound = 0;
259 // { // find the last column number that is not hidden
260 // for ( int iColumn = datasetDimension() - 1;
261 // iColumn < columnCount;
262 // iColumn += datasetDimension() )
263 // if ( ! diagram()->isHidden( iColumn ) )
264 // maxFound = iColumn;
265 // }
266  //maxFound = columnCount;
267  // ^^^ temp
268 
269  LabelPaintCache lpc;
270  LineAttributesInfoList lineList;
271 
272  QVector< qreal > percentSumValues;
273 
274  for ( int column = 0; column < columnCount; ++column )
275  {
276  CartesianDiagramDataCompressor::CachePosition previousCellPosition;
277 
278  //display area can be set by dataset ( == column) and/or by cell
279  LineAttributes laPreviousCell; // by default no area is drawn
280  QModelIndex indexPreviousCell;
281  QList<QPainterPath> areas;
282 
283  for ( int row = 0; row < rowCount; ++row ) {
284  const CartesianDiagramDataCompressor::CachePosition position( row, column );
285  CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
286  const QModelIndex sourceIndex = attributesModel()->mapToSource( point.index );
287 
288  const LineAttributes laCell = diagram()->lineAttributes( sourceIndex );
289  const bool bDisplayCellArea = laCell.displayArea();
290 
292 
293  if ( ISNAN( point.value ) && policy == LineAttributes::MissingValuesShownAsZero )
294  point.value = 0.0;
295 
296  // TODO: revert back to lambdas when we stop caring about pre-C++11
297  // auto valueAt = [&] ( int row, int col ) -> qreal {
298  // if ( row < 0 || row >= rowCount ) {
299  // return NAN;
300  // }
301  //
302  // const CartesianDiagramDataCompressor::CachePosition position( row, col );
303  // const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
304  //
305  // return !ISNAN( point.value ) ? point.value
306  // : policy == LineAttributes::MissingValuesAreBridged ? interpolateMissingValue( position )
307  // : NAN;
308  // };
309  //
310  // auto safeAdd = [] ( qreal accumulator, qreal newValue ) {
311  // return ISNAN( newValue ) ? accumulator : accumulator + newValue;
312  // };
313 
314  struct valueAtLambda {
315  valueAtLambda( int rowCount, StackedLineDiagram* _this, LineAttributes::MissingValuesPolicy policy )
316  : rowCount( rowCount )
317  , _this( _this )
318  , policy( policy )
319  {
320  }
321 
322  int rowCount;
323  StackedLineDiagram* _this;
325 
326  qreal operator() ( int row, int col ) const
327  {
328  if ( row < 0 || row >= rowCount ) {
329  return NAN;
330  }
331 
332  const CartesianDiagramDataCompressor::CachePosition position( row, col );
333  const CartesianDiagramDataCompressor::DataPoint point = _this->compressor().data( position );
334 
335  return !ISNAN( point.value ) ? point.value
336  : policy == LineAttributes::MissingValuesAreBridged ? _this->interpolateMissingValue( position )
337  : NAN;
338  }
339 
340  };
341  valueAtLambda valueAt( rowCount, this, policy );
342 
343 
344  struct safeAddLambda {
345  qreal operator() (qreal accumulator, qreal newValue) const
346  {
347  return ISNAN( newValue ) ? accumulator : accumulator + newValue;
348  }
349  };
350  safeAddLambda safeAdd;
351 
352  qreal nextKey = 0;
353  QVector<qreal> stackedValuesTop( 4, 0.0 );
354  QVector<qreal> stackedValuesBottom( 4, 0.0 );
355 
356  for ( int currentRow = 0; currentRow < 4; ++currentRow ) {
357  stackedValuesTop[currentRow] = safeAdd( stackedValuesTop[currentRow], valueAt( row - 1 + currentRow, column ) );
358  }
359 
360  for ( int column2 = column - 1; column2 >= 0; --column2 )
361  {
362  for ( int currentRow = 0; currentRow < 4; ++currentRow ) {
363  stackedValuesTop[currentRow] = safeAdd( stackedValuesTop[currentRow], valueAt( row - 1 + currentRow, column2 ) );
364  stackedValuesBottom[currentRow] = safeAdd( stackedValuesBottom[currentRow], valueAt( row - 1 + currentRow, column2 ) );
365  }
366  }
367 
368  nextKey = row + 1;
369 
370  // TODO: revert back to lambdas when we stop caring about pre-C++11
371  // auto dataAt = [&] ( const QVector<qreal>& source, qreal key, int index ) {
372  // return ctx->coordinatePlane()->translate( QPointF( diagram()->centerDataPoints() ? key + 0.5 : key, source[index] ) );
373  // };
374  struct dataAtLambda {
375  dataAtLambda( PaintContext* ctx, StackedLineDiagram* _this )
376  : ctx( ctx )
377  , _this( _this )
378  {
379  }
380 
381  PaintContext* ctx;
382  StackedLineDiagram* _this;
383 
384  QPointF operator() ( const QVector<qreal>& source, qreal key, int index ) const
385  {
386  return ctx->coordinatePlane()->translate( QPointF( _this->diagram()->centerDataPoints() ? key + 0.5 : key, source[index] ) );
387  }
388  };
389  dataAtLambda dataAt( ctx, this );
390 
391  const QPointF ptNorthWest = dataAt( stackedValuesTop, point.key, 1 );
392  const QPointF ptSouthWest =
393  bDisplayCellArea ? dataAt( stackedValuesBottom, point.key, 1 )
394  : ptNorthWest;
395 
396  QPointF ptNorthEast;
397  QPointF ptSouthEast;
398 
399  if ( row + 1 < rowCount ) {
400  ptNorthEast = dataAt( stackedValuesTop, nextKey, 2 );
401  lineList.append( LineAttributesInfo( sourceIndex, ptNorthWest, ptNorthEast ) );
402  ptSouthEast =
403  bDisplayCellArea ? dataAt( stackedValuesBottom, nextKey, 2 )
404  : ptNorthEast;
405 
406  if ( areas.count() && laCell != laPreviousCell ) {
407  PaintingHelpers::paintAreas( m_private, ctx, indexPreviousCell, areas, laPreviousCell.transparency() );
408  areas.clear();
409  }
410 
411  if ( bDisplayCellArea ) {
412  QPainterPath path;
413  path.moveTo( ptNorthWest );
414 
415  const QPointF ptBeforeNorthWest =
416  row > 0 ? dataAt( stackedValuesTop, point.key - 1, 0 )
417  : ptNorthWest;
418  const QPointF ptAfterNorthEast =
419  row < rowCount - 2 ? dataAt( stackedValuesTop, point.key + 2, 3 )
420  : ptNorthEast;
421  addSplineChunkTo( path, tension, ptBeforeNorthWest, ptNorthWest, ptNorthEast, ptAfterNorthEast, mainSplineDirection );
422 
423  path.lineTo( ptNorthEast );
424  path.lineTo( ptSouthEast );
425 
426  const QPointF ptBeforeSouthWest =
427  row > 0 ? dataAt( stackedValuesBottom, point.key - 1, 0 )
428  : ptSouthWest;
429  const QPointF ptAfterSouthEast =
430  row < rowCount - 2 ? dataAt( stackedValuesBottom, point.key + 2, 3 )
431  : ptSouthEast;
432  addSplineChunkTo( path, tension, ptAfterSouthEast, ptSouthEast, ptSouthWest, ptBeforeSouthWest, reverseSplineDirection );
433 
434  areas << path;
435  laPreviousCell = laCell;
436  indexPreviousCell = sourceIndex;
437  } else {
438  //qDebug() << "no area shown for row"<<iRow<<" column"<<iColumn;
439  }
440  } else {
441  ptNorthEast = ptNorthWest;
442  ptSouthEast = ptSouthWest;
443  }
444 
445  const PositionPoints pts( ptNorthWest, ptNorthEast, ptSouthEast, ptSouthWest );
446  if ( !ISNAN( point.value ) )
447  m_private->addLabel( &lpc, sourceIndex, &position, pts, Position::NorthWest,
448  Position::NorthWest, point.value );
449  }
450  if ( areas.count() ) {
451  PaintingHelpers::paintAreas( m_private, ctx, indexPreviousCell, areas, laPreviousCell.transparency() );
452  areas.clear();
453  }
454  }
455  PaintingHelpers::paintElements( m_private, ctx, lpc, lineList );
456 }
KDChartEnums::PositionValue value() const
Returns an integer value corresponding to this Position.
void paintElements(AbstractDiagram::Private *diagramPrivate, PaintContext *ctx, const LabelPaintCache &lpc, const LineAttributesInfoList &lineList)
virtual const QPointF translate(const QPointF &diagramPoint) const =0
Translate the given point in value space coordinates to a position in pixel space.
AbstractCoordinatePlane * coordinatePlane() const
Set of attributes for changing the appearance of line charts.
void paintAreas(AbstractDiagram::Private *diagramPrivate, PaintContext *ctx, const QModelIndex &index, const QList< QPolygonF > &areas, uint opacity)
static const Position & NorthWest
LineDiagram defines a common line diagram.
Stores information about painting diagrams.
Stores the absolute target points of a Position.
MissingValuesPolicy
MissingValuesPolicy specifies how a missing value will be shown in a line diagram.
MissingValuesPolicy missingValuesPolicy() const

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/