KD Chart 2  [rev.2.5.1]
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Pages
KDChartCartesianDiagramDataCompressor_p.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 ** Copyright (C) 2001-2013 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 columns = m_model->columnCount( m_rootIndex ) / m_datasetDimension;
281 
282 // if ( columns != m_data.size() )
283 // {
284 // rebuildCache();
285 // }
286 
287  Q_ASSERT( columns == m_data.size() );
288  return columns;
289  } else {
290  return 0;
291  }
292 }
293 
294 int CartesianDiagramDataCompressor::modelDataRows() const
295 {
296  // only operational if there is a model, columns, and a resolution
297  if ( m_model && m_model->columnCount( m_rootIndex ) > 0 && m_xResolution > 0 ) {
298  return m_data.isEmpty() ? 0 : m_data.first().size();
299  } else {
300  return 0;
301  }
302 }
303 
304 void CartesianDiagramDataCompressor::setModel( QAbstractItemModel* model )
305 {
306  if ( m_model != 0 && m_model != model ) {
307  disconnect( m_model, SIGNAL( headerDataChanged( Qt::Orientation, int, int ) ),
308  this, SLOT( slotModelHeaderDataChanged( Qt::Orientation, int, int ) ) );
309  disconnect( m_model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ),
310  this, SLOT( slotModelDataChanged( QModelIndex, QModelIndex ) ) );
311  disconnect( m_model, SIGNAL( layoutChanged() ),
312  this, SLOT( slotModelLayoutChanged() ) );
313  disconnect( m_model, SIGNAL( rowsAboutToBeInserted( QModelIndex, int, int ) ),
314  this, SLOT( slotRowsAboutToBeInserted( QModelIndex, int, int ) ) );
315  disconnect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ),
316  this, SLOT( slotRowsInserted( QModelIndex, int, int ) ) );
317  disconnect( m_model, SIGNAL( rowsAboutToBeRemoved( QModelIndex, int, int ) ),
318  this, SLOT( slotRowsAboutToBeRemoved( QModelIndex, int, int ) ) );
319  disconnect( m_model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ),
320  this, SLOT( slotRowsRemoved( QModelIndex, int, int ) ) );
321  disconnect( m_model, SIGNAL( columnsAboutToBeInserted( QModelIndex, int, int ) ),
322  this, SLOT( slotColumnsAboutToBeInserted( QModelIndex, int, int ) ) );
323  disconnect( m_model, SIGNAL( columnsInserted( QModelIndex, int, int ) ),
324  this, SLOT( slotColumnsInserted( QModelIndex, int, int ) ) );
325  disconnect( m_model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ),
326  this, SLOT( slotColumnsRemoved( QModelIndex, int, int ) ) );
327  disconnect( m_model, SIGNAL( columnsAboutToBeRemoved( QModelIndex, int, int ) ),
328  this, SLOT( slotColumnsAboutToBeRemoved( QModelIndex, int, int ) ) );
329  disconnect( m_model, SIGNAL( modelReset() ),
330  this, SLOT( rebuildCache() ) );
331  m_model = 0;
332  }
333 
334  m_modelCache.setModel( model );
335 
336  if ( model != 0 ) {
337  m_model = model;
338  connect( m_model, SIGNAL( headerDataChanged( Qt::Orientation, int, int ) ),
339  SLOT( slotModelHeaderDataChanged( Qt::Orientation, int, int ) ) );
340  connect( m_model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ),
341  SLOT( slotModelDataChanged( QModelIndex, QModelIndex ) ) );
342  connect( m_model, SIGNAL( layoutChanged() ),
343  SLOT( slotModelLayoutChanged() ) );
344  connect( m_model, SIGNAL( rowsAboutToBeInserted( QModelIndex, int, int ) ),
345  SLOT( slotRowsAboutToBeInserted( QModelIndex, int, int ) ) );
346  connect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ),
347  SLOT( slotRowsInserted( QModelIndex, int, int ) ) );
348  connect( m_model, SIGNAL( rowsAboutToBeRemoved( QModelIndex, int, int ) ),
349  SLOT( slotRowsAboutToBeRemoved( QModelIndex, int, int ) ) );
350  connect( m_model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ),
351  SLOT( slotRowsRemoved( QModelIndex, int, int ) ) );
352  connect( m_model, SIGNAL( columnsAboutToBeInserted( QModelIndex, int, int ) ),
353  SLOT( slotColumnsAboutToBeInserted( QModelIndex, int, int ) ) );
354  connect( m_model, SIGNAL( columnsInserted( QModelIndex, int, int ) ),
355  SLOT( slotColumnsInserted( QModelIndex, int, int ) ) );
356  connect( m_model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ),
357  SLOT( slotColumnsRemoved( QModelIndex, int, int ) ) );
358  connect( m_model, SIGNAL( columnsAboutToBeRemoved( QModelIndex, int, int ) ),
359  SLOT( slotColumnsAboutToBeRemoved( QModelIndex, int, int ) ) );
360  connect( m_model, SIGNAL( modelReset() ),
361  this, SLOT( rebuildCache() ) );
362  }
363  rebuildCache();
364  calculateSampleStepWidth();
365 }
366 
367 void CartesianDiagramDataCompressor::setRootIndex( const QModelIndex& root )
368 {
369  if ( m_rootIndex != root ) {
370  Q_ASSERT( root.model() == m_model || !root.isValid() );
371  m_rootIndex = root;
372  m_modelCache.setRootIndex( root );
373  rebuildCache();
374  calculateSampleStepWidth();
375  }
376 }
377 
378 void CartesianDiagramDataCompressor::recalcResolution()
379 {
380  setResolution( m_xResolution, m_yResolution );
381 }
382 
383 void CartesianDiagramDataCompressor::setResolution( int x, int y )
384 {
385  if ( setResolutionInternal( x, y ) ) {
386  rebuildCache();
387  calculateSampleStepWidth();
388  }
389 }
390 
391 bool CartesianDiagramDataCompressor::setResolutionInternal( int x, int y )
392 {
393  const int oldXRes = m_xResolution;
394  const int oldYRes = m_yResolution;
395 
396  if ( m_datasetDimension != 1 ) {
397  // just ignore the X resolution in that case
398  m_xResolution = m_model ? m_model->rowCount( m_rootIndex ) : 0;
399  } else {
400  m_xResolution = qMax( 0, x );
401  }
402  m_yResolution = qMax( 0, y );
403 
404  return m_xResolution != oldXRes || m_yResolution != oldYRes;
405 }
406 
407 void CartesianDiagramDataCompressor::clearCache()
408 {
409  for ( int column = 0; column < m_data.size(); ++column )
410  m_data[column].fill( DataPoint() );
411 }
412 
413 void CartesianDiagramDataCompressor::rebuildCache()
414 {
415  Q_ASSERT( m_datasetDimension != 0 );
416 
417  m_data.clear();
418  setResolutionInternal( m_xResolution, m_yResolution );
419  const int columnDivisor = m_datasetDimension != 2 ? 1 : m_datasetDimension;
420  const int columnCount = m_model ? m_model->columnCount( m_rootIndex ) / columnDivisor : 0;
421  const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution );
422  m_data.resize( columnCount );
423  for ( int i = 0; i < columnCount; ++i ) {
424  m_data[i].resize( rowCount );
425  }
426  // also empty the attrs cache
427  m_dataValueAttributesCache.clear();
428 }
429 
430 const CartesianDiagramDataCompressor::DataPoint& CartesianDiagramDataCompressor::data( const CachePosition& position ) const
431 {
432  static DataPoint nullDataPoint;
433  if ( ! mapsToModelIndex( position ) ) {
434  return nullDataPoint;
435  }
436  if ( ! isCached( position ) ) {
437  retrieveModelData( position );
438  }
439  return m_data[ position.column ][ position.row ];
440 }
441 
442 QPair< QPointF, QPointF > CartesianDiagramDataCompressor::dataBoundaries() const
443 {
444  const int colCount = modelDataColumns();
445  qreal xMin = std::numeric_limits< qreal >::quiet_NaN();
446  qreal xMax = std::numeric_limits< qreal >::quiet_NaN();
447  qreal yMin = std::numeric_limits< qreal >::quiet_NaN();
448  qreal yMax = std::numeric_limits< qreal >::quiet_NaN();
449 
450  for ( int column = 0; column < colCount; ++column )
451  {
452  const DataPointVector& data = m_data[ column ];
453  int row = 0;
454  for ( DataPointVector::const_iterator it = data.begin(); it != data.end(); ++it, ++row )
455  {
456  const DataPoint& p = *it;
457  if ( !p.index.isValid() )
458  retrieveModelData( CachePosition( row, column ) );
459 
460  const qreal valueX = ISNAN( p.key ) ? 0.0 : p.key;
461  const qreal valueY = ISNAN( p.value ) ? 0.0 : p.value;
462  if ( ISNAN( xMin ) )
463  {
464  xMin = valueX;
465  xMax = valueX;
466  yMin = valueY;
467  yMax = valueY;
468  }
469  else
470  {
471  xMin = qMin( xMin, valueX );
472  xMax = qMax( xMax, valueX );
473  yMin = qMin( yMin, valueY );
474  yMax = qMax( yMax, valueY );
475  }
476  }
477  }
478 
479  // NOTE: calculateDataBoundaries must return the *real* data boundaries!
480  // i.e. we may NOT fake yMin to be qMin( 0.0, yMin )
481  // (khz, 2008-01-24)
482  const QPointF bottomLeft( xMin, yMin );
483  const QPointF topRight( xMax, yMax );
484  return qMakePair( bottomLeft, topRight );
485 }
486 
487 void CartesianDiagramDataCompressor::retrieveModelData( const CachePosition& position ) const
488 {
489  Q_ASSERT( mapsToModelIndex( position ) );
490  DataPoint result;
491  result.hidden = true;
492 
493  switch ( m_mode ) {
494  case Precise:
495  {
496  const QModelIndexList indexes = mapToModel( position );
497 
498  if ( m_datasetDimension == 2 ) {
499  Q_ASSERT( indexes.count() == 2 );
500  const QModelIndex& xIndex = indexes.at( 0 );
501  result.index = xIndex;
502  result.key = m_modelCache.data( xIndex );
503  result.value = m_modelCache.data( indexes.at( 1 ) );
504  } else {
505  if ( indexes.isEmpty() ) {
506  break;
507  }
508  result.value = std::numeric_limits< qreal >::quiet_NaN();
509  result.key = 0.0;
510  Q_FOREACH( const QModelIndex& index, indexes ) {
511  const qreal value = m_modelCache.data( index );
512  if ( !ISNAN( value ) ) {
513  result.value = ISNAN( result.value ) ? value : result.value + value;
514  }
515  result.key += index.row();
516  }
517  result.index = indexes.at( 0 );
518  result.key /= indexes.size();
519  result.value /= indexes.size();
520  }
521 
522  Q_FOREACH( const QModelIndex& index, indexes ) {
523  // the DataPoint point is visible if any of the underlying, aggregated points is visible
524  if ( m_model->data( index, DataHiddenRole ).value<bool>() == false ) {
525  result.hidden = false;
526  }
527  }
528  break;
529  }
530  case SamplingSeven:
531  break;
532  }
533 
534  m_data[ position.column ][ position.row ] = result;
535  Q_ASSERT( isCached( position ) );
536 }
537 
538 CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache(
539  const QModelIndex& index ) const
540 {
541  Q_ASSERT( m_datasetDimension != 0 );
542 
543  static const CachePosition nullPosition;
544  if ( !index.isValid() ) {
545  return nullPosition;
546  }
547  return mapToCache( index.row(), index.column() );
548 }
549 
550 CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache(
551  int row, int column ) const
552 {
553  Q_ASSERT( m_datasetDimension != 0 );
554 
555  if ( m_data.size() == 0 || m_data[ 0 ].size() == 0 ) {
556  return mapToCache( QModelIndex() );
557  }
558  // assumption: indexes per column == 1
559  if ( indexesPerPixel() == 0 ) {
560  return mapToCache( QModelIndex() );
561  }
562  return CachePosition( int( row / indexesPerPixel() ), column / m_datasetDimension );
563 }
564 
565 QModelIndexList CartesianDiagramDataCompressor::mapToModel( const CachePosition& position ) const
566 {
567  QModelIndexList indexes;
568  if ( !mapsToModelIndex( position ) ) {
569  return indexes;
570  }
571 
572  if ( m_datasetDimension == 2 ) {
573  indexes << m_model->index( position.row, position.column * 2, m_rootIndex ); // checked
574  indexes << m_model->index( position.row, position.column * 2 + 1, m_rootIndex ); // checked
575  } else {
576  // assumption: indexes per column == 1 (not true e.g. for stock diagrams with often three or
577  // four dimensions: High-Low-Close or Open-High-Low-Close)
578  Q_ASSERT( position.column < m_model->columnCount( m_rootIndex ) );
579  const qreal ipp = indexesPerPixel();
580  const int baseRow = floor( position.row * ipp );
581  // the following line needs to work for the last row(s), too...
582  const int endRow = floor( ( position.row + 1 ) * ipp );
583  for ( int row = baseRow; row < endRow; ++row ) {
584  Q_ASSERT( row < m_model->rowCount( m_rootIndex ) );
585  const QModelIndex index = m_model->index( row, position.column, m_rootIndex );
586  if ( index.isValid() ) {
587  indexes << index;
588  }
589  }
590  }
591  return indexes;
592 }
593 
594 qreal CartesianDiagramDataCompressor::indexesPerPixel() const
595 {
596  if ( !m_model || m_data.size() == 0 || m_data[ 0 ].size() == 0 ) {
597  return 0;
598  }
599  return qreal( m_model->rowCount( m_rootIndex ) ) / qreal( m_data[ 0 ].size() );
600 }
601 
602 bool CartesianDiagramDataCompressor::mapsToModelIndex( const CachePosition& position ) const
603 {
604  return m_model && m_data.size() > 0 && m_data[ 0 ].size() > 0 &&
605  position.column >= 0 && position.column < m_data.size() &&
606  position.row >=0 && position.row < m_data[ 0 ].size();
607 }
608 
609 void CartesianDiagramDataCompressor::invalidate( const CachePosition& position )
610 {
611  if ( mapsToModelIndex( position ) ) {
612  m_data[ position.column ][ position.row ] = DataPoint();
613  // Also invalidate the data value attributes at "position".
614  // Otherwise the user overwrites the attributes without us noticing
615  // it because we keep reading what's in the cache.
616  m_dataValueAttributesCache.remove( position );
617  }
618 }
619 
620 bool CartesianDiagramDataCompressor::isCached( const CachePosition& position ) const
621 {
622  Q_ASSERT( mapsToModelIndex( position ) );
623  const DataPoint& p = m_data[ position.column ][ position.row ];
624  return p.index.isValid();
625 }
626 
627 void CartesianDiagramDataCompressor::calculateSampleStepWidth()
628 {
629  if ( m_mode == Precise ) {
630  m_sampleStep = 1;
631  return;
632  }
633 
634  static unsigned int SomePrimes[] = {
635  2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47,
636  53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101,
637  151, 211, 313, 401, 503, 607, 701, 811, 911, 1009,
638  10037, 12911, 16001, 20011, 50021,
639  100003, 137867, 199999, 500009, 707753, 1000003, 0
640  }; // ... after that, having a model at all becomes impractical
641 
642  // we want at least 17 samples per data point, using a prime step width
643  const qreal WantedSamples = 17;
644  if ( WantedSamples > indexesPerPixel() ) {
645  m_sampleStep = 1;
646  } else {
647  int i;
648  for ( i = 0; SomePrimes[i] != 0; ++i ) {
649  if ( WantedSamples * SomePrimes[i+1] > indexesPerPixel() ) {
650  break;
651  }
652  }
653  m_sampleStep = SomePrimes[i];
654  if ( SomePrimes[i] == 0 ) {
655  m_sampleStep = SomePrimes[i-1];
656  } else {
657  m_sampleStep = SomePrimes[i];
658  }
659  }
660 }
661 
662 void CartesianDiagramDataCompressor::setDatasetDimension( int dimension )
663 {
664  if ( dimension != m_datasetDimension ) {
665  m_datasetDimension = dimension;
666  rebuildCache();
667  calculateSampleStepWidth();
668  }
669 }

Klarälvdalens Datakonsult AB (KDAB)
Qt-related services and products
http://www.kdab.com/
http://www.kdab.com/products/kd-chart/