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 // FIXME integrate column index retrieval to compressor:
253 // int maxFound = 0;
254 // { // find the last column number that is not hidden
255 // for ( int iColumn = datasetDimension() - 1;
256 // iColumn < columnCount;
257 // iColumn += datasetDimension() )
258 // if ( ! diagram()->isHidden( iColumn ) )
259 // maxFound = iColumn;
260 // }
261  //maxFound = columnCount;
262  // ^^^ temp
263 
264  LabelPaintCache lpc;
265  LineAttributesInfoList lineList;
266 
267  QVector< qreal > percentSumValues;
268 
269  for ( int column = 0; column < columnCount; ++column )
270  {
271  CartesianDiagramDataCompressor::CachePosition previousCellPosition;
272 
273  //display area can be set by dataset ( == column) and/or by cell
274  LineAttributes laPreviousCell; // by default no area is drawn
275  QModelIndex indexPreviousCell;
276  QList<QPainterPath> areas;
277 
278  for ( int row = 0; row < rowCount; ++row ) {
279  const CartesianDiagramDataCompressor::CachePosition position( row, column );
280  CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
281  const QModelIndex sourceIndex = attributesModel()->mapToSource( point.index );
282 
283  const LineAttributes laCell = diagram()->lineAttributes( sourceIndex );
284  const bool bDisplayCellArea = laCell.displayArea();
285 
287 
288  if ( ISNAN( point.value ) && policy == LineAttributes::MissingValuesShownAsZero )
289  point.value = 0.0;
290 
291  // TODO: revert back to lambdas when we stop caring about pre-C++11
292  // auto valueAt = [&] ( int row, int col ) -> qreal {
293  // if ( row < 0 || row >= rowCount ) {
294  // return NAN;
295  // }
296  //
297  // const CartesianDiagramDataCompressor::CachePosition position( row, col );
298  // const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
299  //
300  // return !ISNAN( point.value ) ? point.value
301  // : policy == LineAttributes::MissingValuesAreBridged ? interpolateMissingValue( position )
302  // : NAN;
303  // };
304  //
305  // auto safeAdd = [] ( qreal accumulator, qreal newValue ) {
306  // return ISNAN( newValue ) ? accumulator : accumulator + newValue;
307  // };
308 
309  struct valueAtLambda {
310  valueAtLambda( int rowCount, StackedLineDiagram* _this, LineAttributes::MissingValuesPolicy policy )
311  : rowCount( rowCount )
312  , _this( _this )
313  , policy( policy )
314  {
315  }
316 
317  int rowCount;
318  StackedLineDiagram* _this;
320 
321  qreal operator() ( int row, int col ) const
322  {
323  if ( row < 0 || row >= rowCount ) {
324  return NAN;
325  }
326 
327  const CartesianDiagramDataCompressor::CachePosition position( row, col );
328  const CartesianDiagramDataCompressor::DataPoint point = _this->compressor().data( position );
329 
330  return !ISNAN( point.value ) ? point.value
331  : policy == LineAttributes::MissingValuesAreBridged ? _this->interpolateMissingValue( position )
332  : NAN;
333  }
334 
335  };
336  valueAtLambda valueAt( rowCount, this, policy );
337 
338 
339  struct safeAddLambda {
340  qreal operator() (qreal accumulator, qreal newValue) const
341  {
342  return ISNAN( newValue ) ? accumulator : accumulator + newValue;
343  }
344  };
345  safeAddLambda safeAdd;
346 
347  qreal nextKey = 0;
348  QVector<qreal> stackedValuesTop( 4, 0.0 );
349  QVector<qreal> stackedValuesBottom( 4, 0.0 );
350 
351  for ( int currentRow = 0; currentRow < 4; ++currentRow ) {
352  stackedValuesTop[currentRow] = safeAdd( stackedValuesTop[currentRow], valueAt( row - 1 + currentRow, column ) );
353  }
354 
355  for ( int column2 = column - 1; column2 >= 0; --column2 )
356  {
357  for ( int currentRow = 0; currentRow < 4; ++currentRow ) {
358  stackedValuesTop[currentRow] = safeAdd( stackedValuesTop[currentRow], valueAt( row - 1 + currentRow, column2 ) );
359  stackedValuesBottom[currentRow] = safeAdd( stackedValuesBottom[currentRow], valueAt( row - 1 + currentRow, column2 ) );
360  }
361  }
362 
363  nextKey = row + 1;
364 
365  // TODO: revert back to lambdas when we stop caring about pre-C++11
366  // auto dataAt = [&] ( const QVector<qreal>& source, qreal key, int index ) {
367  // return ctx->coordinatePlane()->translate( QPointF( diagram()->centerDataPoints() ? key + 0.5 : key, source[index] ) );
368  // };
369  struct dataAtLambda {
370  dataAtLambda( PaintContext* ctx, StackedLineDiagram* _this )
371  : ctx( ctx )
372  , _this( _this )
373  {
374  }
375 
376  PaintContext* ctx;
377  StackedLineDiagram* _this;
378 
379  QPointF operator() ( const QVector<qreal>& source, qreal key, int index ) const
380  {
381  return ctx->coordinatePlane()->translate( QPointF( _this->diagram()->centerDataPoints() ? key + 0.5 : key, source[index] ) );
382  }
383  };
384  dataAtLambda dataAt( ctx, this );
385 
386  const QPointF ptNorthWest = dataAt( stackedValuesTop, point.key, 1 );
387  const QPointF ptSouthWest =
388  bDisplayCellArea ? dataAt( stackedValuesBottom, point.key, 1 )
389  : ptNorthWest;
390 
391  QPointF ptNorthEast;
392  QPointF ptSouthEast;
393 
394  if ( row + 1 < rowCount ) {
395  ptNorthEast = dataAt( stackedValuesTop, nextKey, 2 );
396  lineList.append( LineAttributesInfo( sourceIndex, ptNorthWest, ptNorthEast ) );
397  ptSouthEast =
398  bDisplayCellArea ? dataAt( stackedValuesBottom, nextKey, 2 )
399  : ptNorthEast;
400 
401  if ( areas.count() && laCell != laPreviousCell ) {
402  PaintingHelpers::paintAreas( m_private, ctx, indexPreviousCell, areas, laPreviousCell.transparency() );
403  areas.clear();
404  }
405 
406  if ( bDisplayCellArea ) {
407  QPainterPath path;
408  path.moveTo( ptNorthWest );
409 
410  const QPointF ptBeforeNorthWest =
411  row > 0 ? dataAt( stackedValuesTop, point.key - 1, 0 )
412  : ptNorthWest;
413  const QPointF ptAfterNorthEast =
414  row < rowCount - 1 ? dataAt( stackedValuesTop, point.key + 2, 3 )
415  : ptNorthEast;
416  addSplineChunkTo( path, tension, ptBeforeNorthWest, ptNorthWest, ptNorthEast, ptAfterNorthEast );
417 
418  path.lineTo( ptNorthEast );
419  path.lineTo( ptSouthEast );
420 
421  const QPointF ptBeforeSouthWest =
422  row > 0 ? dataAt( stackedValuesBottom, point.key - 1, 0 )
423  : ptSouthWest;
424  const QPointF ptAfterSouthEast =
425  row < rowCount - 1 ? dataAt( stackedValuesBottom, point.key + 2, 3 )
426  : ptSouthEast;
427  addSplineChunkTo( path, tension, ptAfterSouthEast, ptSouthEast, ptSouthWest, ptBeforeSouthWest, ReverseSplineDirection );
428 
429  areas << path;
430  laPreviousCell = laCell;
431  indexPreviousCell = sourceIndex;
432  } else {
433  //qDebug() << "no area shown for row"<<iRow<<" column"<<iColumn;
434  }
435  } else {
436  ptNorthEast = ptNorthWest;
437  ptSouthEast = ptSouthWest;
438  }
439 
440  const PositionPoints pts( ptNorthWest, ptNorthEast, ptSouthEast, ptSouthWest );
441  if ( !ISNAN( point.value ) )
442  m_private->addLabel( &lpc, sourceIndex, &position, pts, Position::NorthWest,
443  Position::NorthWest, point.value );
444  }
445  if ( areas.count() ) {
446  PaintingHelpers::paintAreas( m_private, ctx, indexPreviousCell, areas, laPreviousCell.transparency() );
447  areas.clear();
448  }
449  }
450  PaintingHelpers::paintElements( m_private, ctx, lpc, lineList );
451 }
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/