KD Chart 2  [rev.2.7]
KDChartCartesianDiagramDataCompressor_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 "KDChartCartesianDiagramDataCompressor_p.h"
24 
25 #include <QtDebug>
26 #include <QAbstractItemModel>
27 
29 
30 #include <KDABLibFakes>
31 
32 using namespace KDChart;
33 using namespace std;
34 
35 CartesianDiagramDataCompressor::CartesianDiagramDataCompressor( QObject* parent )
36  : QObject( parent )
37  , m_mode( Precise )
38  , m_xResolution( 0 )
39  , m_yResolution( 0 )
40  , m_sampleStep( 0 )
41  , m_datasetDimension( 1 )
42 {
43  calculateSampleStepWidth();
44  m_data.resize( 0 );
45 }
46 
47 static bool contains( const CartesianDiagramDataCompressor::AggregatedDataValueAttributes& aggregated,
48  const DataValueAttributes& attributes )
49 {
50  CartesianDiagramDataCompressor::AggregatedDataValueAttributes::const_iterator it = aggregated.constBegin();
51  for ( ; it != aggregated.constEnd(); ++it ) {
52  if ( it.value() == attributes ) {
53  return true;
54  }
55  }
56  return false;
57 }
58 
59 CartesianDiagramDataCompressor::AggregatedDataValueAttributes CartesianDiagramDataCompressor::aggregatedAttrs(
60  const AbstractDiagram* diagram,
61  const QModelIndex & index,
62  const CachePosition& position ) const
63 {
64  // return cached attrs, if any
65  DataValueAttributesCache::const_iterator i = m_dataValueAttributesCache.constFind( position );
66  if ( i != m_dataValueAttributesCache.constEnd() ) {
67  return i.value();
68  }
69 
70  // aggregate attributes from all indices in the same CachePosition as index
71  CartesianDiagramDataCompressor::AggregatedDataValueAttributes aggregated;
72  KDAB_FOREACH( const QModelIndex& neighborIndex, mapToModel( position ) ) {
73  DataValueAttributes attrs = diagram->dataValueAttributes( neighborIndex );
74  // only store visible and unique attributes
75  if ( !attrs.isVisible() ) {
76  continue;
77  }
78  if ( !contains( aggregated, attrs ) ) {
79  aggregated[ neighborIndex ] = attrs;
80  }
81  }
82  // if none of the attributes had the visible flag set, we just take the one set for the index
83  // to avoid returning an empty list (### why not return an empty list?)
84  if ( aggregated.isEmpty() ) {
85  aggregated[index] = diagram->dataValueAttributes( index );
86  }
87 
88  m_dataValueAttributesCache[position] = aggregated;
89  return aggregated;
90 }
91 
92 bool CartesianDiagramDataCompressor::prepareDataChange( const QModelIndex& parent, bool isRows,
93  int* start, int* end )
94 {
95  if ( parent != m_rootIndex ) {
96  return false;
97  }
98  Q_ASSERT( *start <= *end );
99 
100  CachePosition startPos = isRows ? mapToCache( *start, 0 ) : mapToCache( 0, *start );
101  CachePosition endPos = isRows ? mapToCache( *end, 0 ) : mapToCache( 0, *end );
102 
103  static const CachePosition nullPosition;
104  if ( startPos == nullPosition ) {
105  rebuildCache();
106  startPos = isRows ? mapToCache( *start, 0 ) : mapToCache( 0, *start );
107  endPos = isRows ? mapToCache( *end, 0 ) : mapToCache( 0, *end );
108  // The start position still isn't valid,
109  // means that no resolution was set yet or we're about to add the first rows
110  if ( startPos == nullPosition ) {
111  return false;
112  }
113  }
114 
115  *start = isRows ? startPos.row : startPos.column;
116  *end = isRows ? endPos.row : endPos.column;
117  return true;
118 }
119 
120 void CartesianDiagramDataCompressor::slotRowsAboutToBeInserted( const QModelIndex& parent, int start, int end )
121 {
122  if ( !prepareDataChange( parent, true, &start, &end ) ) {
123  return;
124  }
125  for ( int i = 0; i < m_data.size(); ++i )
126  {
127  Q_ASSERT( start >= 0 && start <= m_data[ i ].size() );
128  m_data[ i ].insert( start, end - start + 1, DataPoint() );
129  }
130 }
131 
132 void CartesianDiagramDataCompressor::slotRowsInserted( const QModelIndex& parent, int start, int end )
133 {
134  if ( !prepareDataChange( parent, true, &start, &end ) ) {
135  return;
136  }
137  for ( int i = 0; i < m_data.size(); ++i )
138  {
139  for ( int j = start; j < m_data[i].size(); ++j ) {
140  retrieveModelData( CachePosition( j, i ) );
141  }
142  }
143 }
144 
145 void CartesianDiagramDataCompressor::slotColumnsAboutToBeInserted( const QModelIndex& parent, int start, int end )
146 {
147  if ( !prepareDataChange( parent, false, &start, &end ) ) {
148  return;
149  }
150  const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution );
151  Q_ASSERT( start >= 0 && start <= m_data.size() );
152  m_data.insert( start, end - start + 1, QVector< DataPoint >( rowCount ) );
153 }
154 
155 void CartesianDiagramDataCompressor::slotColumnsInserted( const QModelIndex& parent, int start, int end )
156 {
157  if ( !prepareDataChange( parent, false, &start, &end ) ) {
158  return;
159  }
160  for ( int i = start; i < m_data.size(); ++i )
161  {
162  for (int j = 0; j < m_data[i].size(); ++j ) {
163  retrieveModelData( CachePosition( j, i ) );
164  }
165  }
166 }
167 
168 void CartesianDiagramDataCompressor::slotRowsAboutToBeRemoved( const QModelIndex& parent, int start, int end )
169 {
170  if ( !prepareDataChange( parent, true, &start, &end ) ) {
171  return;
172  }
173  for ( int i = 0; i < m_data.size(); ++i ) {
174  m_data[ i ].remove( start, end - start + 1 );
175  }
176 }
177 
178 void CartesianDiagramDataCompressor::slotRowsRemoved( const QModelIndex& parent, int start, int end )
179 {
180  if ( parent != m_rootIndex )
181  return;
182  Q_ASSERT( start <= end );
183  Q_UNUSED( end )
184 
185  CachePosition startPos = mapToCache( start, 0 );
186  static const CachePosition nullPosition;
187  if ( startPos == nullPosition ) {
188  // Since we should already have rebuilt the cache, it won't help to rebuild it again.
189  // Do not Q_ASSERT() though, since the resolution might simply not be set or we might now have 0 rows
190  return;
191  }
192 
193  for ( int i = 0; i < m_data.size(); ++i ) {
194  for (int j = startPos.row; j < m_data[i].size(); ++j ) {
195  retrieveModelData( CachePosition( j, i ) );
196  }
197  }
198 }
199 
200 void CartesianDiagramDataCompressor::slotColumnsAboutToBeRemoved( const QModelIndex& parent, int start, int end )
201 {
202  if ( !prepareDataChange( parent, false, &start, &end ) ) {
203  return;
204  }
205  m_data.remove( start, end - start + 1 );
206 }
207 
208 void CartesianDiagramDataCompressor::slotColumnsRemoved( const QModelIndex& parent, int start, int end )
209 {
210  if ( parent != m_rootIndex )
211  return;
212  Q_ASSERT( start <= end );
213  Q_UNUSED( end );
214 
215  const CachePosition startPos = mapToCache( 0, start );
216 
217  static const CachePosition nullPosition;
218  if ( startPos == nullPosition ) {
219  // Since we should already have rebuilt the cache, it won't help to rebuild it again.
220  // Do not Q_ASSERT() though, since the resolution might simply not be set or we might now have 0 columns
221  return;
222  }
223 
224  for ( int i = startPos.column; i < m_data.size(); ++i ) {
225  for ( int j = 0; j < m_data[i].size(); ++j ) {
226  retrieveModelData( CachePosition( j, i ) );
227  }
228  }
229 }
230 
231 void CartesianDiagramDataCompressor::slotModelHeaderDataChanged( Qt::Orientation orientation, int first, int last )
232 {
233  if ( orientation != Qt::Vertical )
234  return;
235 
236  if ( m_model->rowCount( m_rootIndex ) > 0 ) {
237  const QModelIndex firstRow = m_model->index( 0, first, m_rootIndex ); // checked
238  const QModelIndex lastRow = m_model->index( m_model->rowCount( m_rootIndex ) - 1, last, m_rootIndex ); // checked
239 
240  slotModelDataChanged( firstRow, lastRow );
241  }
242 }
243 
244 void CartesianDiagramDataCompressor::slotModelDataChanged(
245  const QModelIndex& topLeftIndex,
246  const QModelIndex& bottomRightIndex )
247 {
248  if ( topLeftIndex.parent() != m_rootIndex )
249  return;
250  Q_ASSERT( topLeftIndex.parent() == bottomRightIndex.parent() );
251  Q_ASSERT( topLeftIndex.row() <= bottomRightIndex.row() );
252  Q_ASSERT( topLeftIndex.column() <= bottomRightIndex.column() );
253  CachePosition topleft = mapToCache( topLeftIndex );
254  CachePosition bottomright = mapToCache( bottomRightIndex );
255  for ( int row = topleft.row; row <= bottomright.row; ++row )
256  for ( int column = topleft.column; column <= bottomright.column; ++column )
257  invalidate( CachePosition( row, column ) );
258 }
259 
260 void CartesianDiagramDataCompressor::slotModelLayoutChanged()
261 {
262  rebuildCache();
263  calculateSampleStepWidth();
264 }
265 
266 void CartesianDiagramDataCompressor::slotDiagramLayoutChanged( AbstractDiagram* diagramBase )
267 {
268  AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* >( diagramBase );
269  Q_ASSERT( diagram );
270  if ( diagram->datasetDimension() != m_datasetDimension ) {
271  setDatasetDimension( diagram->datasetDimension() );
272  }
273 }
274 
275 int CartesianDiagramDataCompressor::modelDataColumns() const
276 {
277  Q_ASSERT( m_datasetDimension != 0 );
278  // only operational if there is a model and a resolution
279  if ( m_model ) {
280  const int effectiveDimension = m_datasetDimension == 2 ? 2 : 1;
281  const int columns = m_model->columnCount( m_rootIndex ) / effectiveDimension;
282  Q_ASSERT( columns == m_data.size() );
283  return columns;
284  } else {
285  return 0;
286  }
287 }
288 
289 int CartesianDiagramDataCompressor::modelDataRows() const
290 {
291  // only operational if there is a model, columns, and a resolution
292  if ( m_model && m_model->columnCount( m_rootIndex ) > 0 && m_xResolution > 0 ) {
293  return m_data.isEmpty() ? 0 : m_data.first().size();
294  } else {
295  return 0;
296  }
297 }
298 
299 void CartesianDiagramDataCompressor::setModel( QAbstractItemModel* model )
300 {
301  if ( model == m_model ) {
302  return;
303  }
304 
305  if ( m_model != 0 ) {
306  disconnect( m_model, SIGNAL( headerDataChanged( Qt::Orientation, int, int ) ),
307  this, SLOT( slotModelHeaderDataChanged( Qt::Orientation, int, int ) ) );
308  disconnect( m_model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ),
309  this, SLOT( slotModelDataChanged( QModelIndex, QModelIndex ) ) );
310  disconnect( m_model, SIGNAL( layoutChanged() ),
311  this, SLOT( slotModelLayoutChanged() ) );
312  disconnect( m_model, SIGNAL( rowsAboutToBeInserted( QModelIndex, int, int ) ),
313  this, SLOT( slotRowsAboutToBeInserted( QModelIndex, int, int ) ) );
314  disconnect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ),
315  this, SLOT( slotRowsInserted( QModelIndex, int, int ) ) );
316  disconnect( m_model, SIGNAL( rowsAboutToBeRemoved( QModelIndex, int, int ) ),
317  this, SLOT( slotRowsAboutToBeRemoved( QModelIndex, int, int ) ) );
318  disconnect( m_model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ),
319  this, SLOT( slotRowsRemoved( QModelIndex, int, int ) ) );
320  disconnect( m_model, SIGNAL( columnsAboutToBeInserted( QModelIndex, int, int ) ),
321  this, SLOT( slotColumnsAboutToBeInserted( QModelIndex, int, int ) ) );
322  disconnect( m_model, SIGNAL( columnsInserted( QModelIndex, int, int ) ),
323  this, SLOT( slotColumnsInserted( QModelIndex, int, int ) ) );
324  disconnect( m_model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ),
325  this, SLOT( slotColumnsRemoved( QModelIndex, int, int ) ) );
326  disconnect( m_model, SIGNAL( columnsAboutToBeRemoved( QModelIndex, int, int ) ),
327  this, SLOT( slotColumnsAboutToBeRemoved( QModelIndex, int, int ) ) );
328  disconnect( m_model, SIGNAL( modelReset() ),
329  this, SLOT( rebuildCache() ) );
330  m_model = 0;
331  }
332 
333  m_modelCache.setModel( model );
334 
335  if ( model != 0 ) {
336  m_model = model;
337  connect( m_model, SIGNAL( headerDataChanged( Qt::Orientation, int, int ) ),
338  SLOT( slotModelHeaderDataChanged( Qt::Orientation, int, int ) ) );
339  connect( m_model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ),
340  SLOT( slotModelDataChanged( QModelIndex, QModelIndex ) ) );
341  connect( m_model, SIGNAL( layoutChanged() ),
342  SLOT( slotModelLayoutChanged() ) );
343  connect( m_model, SIGNAL( rowsAboutToBeInserted( QModelIndex, int, int ) ),
344  SLOT( slotRowsAboutToBeInserted( QModelIndex, int, int ) ) );
345  connect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ),
346  SLOT( slotRowsInserted( QModelIndex, int, int ) ) );
347  connect( m_model, SIGNAL( rowsAboutToBeRemoved( QModelIndex, int, int ) ),
348  SLOT( slotRowsAboutToBeRemoved( QModelIndex, int, int ) ) );
349  connect( m_model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ),
350  SLOT( slotRowsRemoved( QModelIndex, int, int ) ) );
351  connect( m_model, SIGNAL( columnsAboutToBeInserted( QModelIndex, int, int ) ),
352  SLOT( slotColumnsAboutToBeInserted( QModelIndex, int, int ) ) );
353  connect( m_model, SIGNAL( columnsInserted( QModelIndex, int, int ) ),
354  SLOT( slotColumnsInserted( QModelIndex, int, int ) ) );
355  connect( m_model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ),
356  SLOT( slotColumnsRemoved( QModelIndex, int, int ) ) );
357  connect( m_model, SIGNAL( columnsAboutToBeRemoved( QModelIndex, int, int ) ),
358  SLOT( slotColumnsAboutToBeRemoved( QModelIndex, int, int ) ) );
359  connect( m_model, SIGNAL( modelReset() ), SLOT( rebuildCache() ) );
360  }
361  rebuildCache();
362  calculateSampleStepWidth();
363 }
364 
365 void CartesianDiagramDataCompressor::setRootIndex( const QModelIndex& root )
366 {
367  if ( m_rootIndex != root ) {
368  Q_ASSERT( root.model() == m_model || !root.isValid() );
369  m_rootIndex = root;
370  m_modelCache.setRootIndex( root );
371  rebuildCache();
372  calculateSampleStepWidth();
373  }
374 }
375 
376 void CartesianDiagramDataCompressor::recalcResolution()
377 {
378  setResolution( m_xResolution, m_yResolution );
379 }
380 
381 void CartesianDiagramDataCompressor::setResolution( int x, int y )
382 {
383  if ( setResolutionInternal( x, y ) ) {
384  rebuildCache();
385  calculateSampleStepWidth();
386  }
387 }
388 
389 bool CartesianDiagramDataCompressor::setResolutionInternal( int x, int y )
390 {
391  const int oldXRes = m_xResolution;
392  const int oldYRes = m_yResolution;
393 
394  if ( m_datasetDimension != 1 ) {
395  // just ignore the X resolution in that case
396  m_xResolution = m_model ? m_model->rowCount( m_rootIndex ) : 0;
397  } else {
398  m_xResolution = qMax( 0, x );
399  }
400  m_yResolution = qMax( 0, y );
401 
402  return m_xResolution != oldXRes || m_yResolution != oldYRes;
403 }
404 
405 void CartesianDiagramDataCompressor::clearCache()
406 {
407  for ( int column = 0; column < m_data.size(); ++column )
408  m_data[column].fill( DataPoint() );
409 }
410 
411 void CartesianDiagramDataCompressor::rebuildCache()
412 {
413  Q_ASSERT( m_datasetDimension != 0 );
414 
415  m_data.clear();
416  setResolutionInternal( m_xResolution, m_yResolution );
417  const int columnDivisor = m_datasetDimension == 2 ? 2 : 1;
418  const int columnCount = m_model ? m_model->columnCount( m_rootIndex ) / columnDivisor : 0;
419  const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution );
420  m_data.resize( columnCount );
421  for ( int i = 0; i < columnCount; ++i ) {
422  m_data[i].resize( rowCount );
423  }
424  // also empty the attrs cache
425  m_dataValueAttributesCache.clear();
426 }
427 
428 const CartesianDiagramDataCompressor::DataPoint& CartesianDiagramDataCompressor::data( const CachePosition& position ) const
429 {
430  static DataPoint nullDataPoint;
431  if ( ! mapsToModelIndex( position ) ) {
432  return nullDataPoint;
433  }
434  if ( ! isCached( position ) ) {
435  retrieveModelData( position );
436  }
437  return m_data[ position.column ][ position.row ];
438 }
439 
440 QPair< QPointF, QPointF > CartesianDiagramDataCompressor::dataBoundaries() const
441 {
442  const int colCount = modelDataColumns();
443  qreal xMin = std::numeric_limits< qreal >::quiet_NaN();
444  qreal xMax = std::numeric_limits< qreal >::quiet_NaN();
445  qreal yMin = std::numeric_limits< qreal >::quiet_NaN();
446  qreal yMax = std::numeric_limits< qreal >::quiet_NaN();
447 
448  for ( int column = 0; column < colCount; ++column )
449  {
450  const DataPointVector& data = m_data[ column ];
451  int row = 0;
452  for ( DataPointVector::const_iterator it = data.begin(); it != data.end(); ++it, ++row )
453  {
454  const DataPoint& p = *it;
455  if ( !p.index.isValid() )
456  retrieveModelData( CachePosition( row, column ) );
457 
458  if ( ISNAN( p.key ) || ISNAN( p.value ) ) {
459  continue;
460  }
461 
462  if ( ISNAN( xMin ) ) {
463  xMin = p.key;
464  xMax = p.key;
465  yMin = p.value;
466  yMax = p.value;
467  } else {
468  xMin = qMin( xMin, p.key );
469  xMax = qMax( xMax, p.key );
470  yMin = qMin( yMin, p.value );
471  yMax = qMax( yMax, p.value );
472  }
473  }
474  }
475 
476  const QPointF bottomLeft( xMin, yMin );
477  const QPointF topRight( xMax, yMax );
478  return qMakePair( bottomLeft, topRight );
479 }
480 
481 void CartesianDiagramDataCompressor::retrieveModelData( const CachePosition& position ) const
482 {
483  Q_ASSERT( mapsToModelIndex( position ) );
484  DataPoint result;
485  result.hidden = true;
486 
487  switch ( m_mode ) {
488  case Precise:
489  {
490  const QModelIndexList indexes = mapToModel( position );
491 
492  if ( m_datasetDimension == 2 ) {
493  Q_ASSERT( indexes.count() == 2 );
494  const QModelIndex& xIndex = indexes.at( 0 );
495  result.index = xIndex;
496  result.key = m_modelCache.data( xIndex );
497  result.value = m_modelCache.data( indexes.at( 1 ) );
498  } else {
499  if ( indexes.isEmpty() ) {
500  break;
501  }
502  result.value = std::numeric_limits< qreal >::quiet_NaN();
503  result.key = 0.0;
504  Q_FOREACH( const QModelIndex& index, indexes ) {
505  const qreal value = m_modelCache.data( index );
506  if ( !ISNAN( value ) ) {
507  result.value = ISNAN( result.value ) ? value : result.value + value;
508  }
509  result.key += index.row();
510  }
511  result.index = indexes.at( 0 );
512  result.key /= indexes.size();
513  result.value /= indexes.size();
514  }
515 
516  Q_FOREACH( const QModelIndex& index, indexes ) {
517  // the DataPoint point is visible if any of the underlying, aggregated points is visible
518  if ( m_model->data( index, DataHiddenRole ).value<bool>() == false ) {
519  result.hidden = false;
520  }
521  }
522  break;
523  }
524  case SamplingSeven:
525  break;
526  }
527 
528  m_data[ position.column ][ position.row ] = result;
529  Q_ASSERT( isCached( position ) );
530 }
531 
532 CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache(
533  const QModelIndex& index ) const
534 {
535  Q_ASSERT( m_datasetDimension != 0 );
536 
537  static const CachePosition nullPosition;
538  if ( !index.isValid() ) {
539  return nullPosition;
540  }
541  return mapToCache( index.row(), index.column() );
542 }
543 
544 CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache(
545  int row, int column ) const
546 {
547  Q_ASSERT( m_datasetDimension != 0 );
548 
549  if ( m_data.size() == 0 || m_data[ 0 ].size() == 0 ) {
550  return mapToCache( QModelIndex() );
551  }
552  // assumption: indexes per column == 1
553  if ( indexesPerPixel() == 0 ) {
554  return mapToCache( QModelIndex() );
555  }
556  return CachePosition( int( row / indexesPerPixel() ), column / m_datasetDimension );
557 }
558 
559 QModelIndexList CartesianDiagramDataCompressor::mapToModel( const CachePosition& position ) const
560 {
561  QModelIndexList indexes;
562  if ( !mapsToModelIndex( position ) ) {
563  return indexes;
564  }
565 
566  Q_ASSERT( position.column < modelDataColumns() );
567  if ( m_datasetDimension == 2 ) {
568  indexes << m_model->index( position.row, position.column * 2, m_rootIndex ); // checked
569  indexes << m_model->index( position.row, position.column * 2 + 1, m_rootIndex ); // checked
570  } else {
571  // here, indexes per column is usually but not always 1 (e.g. stock diagrams can have three
572  // or four dimensions: High-Low-Close or Open-High-Low-Close)
573  const qreal ipp = indexesPerPixel();
574  const int baseRow = floor( position.row * ipp );
575  // the following line needs to work for the last row(s), too...
576  const int endRow = floor( ( position.row + 1 ) * ipp );
577  for ( int row = baseRow; row < endRow; ++row ) {
578  Q_ASSERT( row < m_model->rowCount( m_rootIndex ) );
579  const QModelIndex index = m_model->index( row, position.column, m_rootIndex );
580  if ( index.isValid() ) {
581  indexes << index;
582  }
583  }
584  }
585  return indexes;
586 }
587 
588 qreal CartesianDiagramDataCompressor::indexesPerPixel() const
589 {
590  if ( !m_model || m_data.size() == 0 || m_data[ 0 ].size() == 0 ) {
591  return 0;
592  }
593  return qreal( m_model->rowCount( m_rootIndex ) ) / qreal( m_data[ 0 ].size() );
594 }
595 
596 bool CartesianDiagramDataCompressor::mapsToModelIndex( const CachePosition& position ) const
597 {
598  return m_model && m_data.size() > 0 && m_data[ 0 ].size() > 0 &&
599  position.column >= 0 && position.column < m_data.size() &&
600  position.row >=0 && position.row < m_data[ 0 ].size();
601 }
602 
603 void CartesianDiagramDataCompressor::invalidate( const CachePosition& position )
604 {
605  if ( mapsToModelIndex( position ) ) {
606  m_data[ position.column ][ position.row ] = DataPoint();
607  // Also invalidate the data value attributes at "position".
608  // Otherwise the user overwrites the attributes without us noticing
609  // it because we keep reading what's in the cache.
610  m_dataValueAttributesCache.remove( position );
611  }
612 }
613 
614 bool CartesianDiagramDataCompressor::isCached( const CachePosition& position ) const
615 {
616  Q_ASSERT( mapsToModelIndex( position ) );
617  const DataPoint& p = m_data[ position.column ][ position.row ];
618  return p.index.isValid();
619 }
620 
621 void CartesianDiagramDataCompressor::calculateSampleStepWidth()
622 {
623  if ( m_mode == Precise ) {
624  m_sampleStep = 1;
625  return;
626  }
627 
628  static const unsigned int SomePrimes[] = {
629  2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47,
630  53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101,
631  151, 211, 313, 401, 503, 607, 701, 811, 911, 1009,
632  10037, 12911, 16001, 20011, 50021,
633  100003, 137867, 199999, 500009, 707753, 1000003, 0
634  }; // ... after that, having a model at all becomes impractical
635 
636  // we want at least 17 samples per data point, using a prime step width
637  const qreal WantedSamples = 17;
638  if ( WantedSamples > indexesPerPixel() ) {
639  m_sampleStep = 1;
640  } else {
641  int i;
642  for ( i = 0; SomePrimes[i] != 0; ++i ) {
643  if ( WantedSamples * SomePrimes[i+1] > indexesPerPixel() ) {
644  break;
645  }
646  }
647  m_sampleStep = SomePrimes[i];
648  if ( SomePrimes[i] == 0 ) {
649  m_sampleStep = SomePrimes[i-1];
650  } else {
651  m_sampleStep = SomePrimes[i];
652  }
653  }
654 }
655 
656 void CartesianDiagramDataCompressor::setDatasetDimension( int dimension )
657 {
658  if ( dimension != m_datasetDimension ) {
659  m_datasetDimension = dimension;
660  rebuildCache();
661  calculateSampleStepWidth();
662  }
663 }
DataValueAttributes dataValueAttributes() const
Retrieve the DataValueAttributes specified globally.
Diagram attributes dealing with data value labels.
AbstractDiagram defines the interface for diagram classes.
Base class for diagrams based on a cartesian coordianate system.
Class only listed here to document inheritance of some KDChart classes.
static bool contains(const CartesianDiagramDataCompressor::AggregatedDataValueAttributes &aggregated, const DataValueAttributes &attributes)
int datasetDimension() const
The dataset dimension of a diagram determines how many value dimensions it expects each datapoint to ...

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/