23 #include "KDChartCartesianDiagramDataCompressor_p.h" 26 #include <QAbstractItemModel> 30 #include <KDABLibFakes> 35 CartesianDiagramDataCompressor::CartesianDiagramDataCompressor(
QObject* parent )
41 , m_datasetDimension( 1 )
43 calculateSampleStepWidth();
47 static bool contains(
const CartesianDiagramDataCompressor::AggregatedDataValueAttributes& aggregated,
50 CartesianDiagramDataCompressor::AggregatedDataValueAttributes::const_iterator it = aggregated.constBegin();
51 for ( ; it != aggregated.constEnd(); ++it ) {
52 if ( it.value() == attributes ) {
59 CartesianDiagramDataCompressor::AggregatedDataValueAttributes CartesianDiagramDataCompressor::aggregatedAttrs(
61 const QModelIndex & index,
62 const CachePosition& position )
const 65 DataValueAttributesCache::const_iterator i = m_dataValueAttributesCache.constFind( position );
66 if ( i != m_dataValueAttributesCache.constEnd() ) {
71 CartesianDiagramDataCompressor::AggregatedDataValueAttributes aggregated;
72 KDAB_FOREACH(
const QModelIndex& neighborIndex, mapToModel( position ) ) {
78 if ( !
contains( aggregated, attrs ) ) {
79 aggregated[ neighborIndex ] = attrs;
84 if ( aggregated.isEmpty() ) {
88 m_dataValueAttributesCache[position] = aggregated;
92 bool CartesianDiagramDataCompressor::prepareDataChange(
const QModelIndex& parent,
bool isRows,
93 int* start,
int* end )
95 if ( parent != m_rootIndex ) {
98 Q_ASSERT( *start <= *end );
100 CachePosition startPos = isRows ? mapToCache( *start, 0 ) : mapToCache( 0, *start );
101 CachePosition endPos = isRows ? mapToCache( *end, 0 ) : mapToCache( 0, *end );
103 static const CachePosition nullPosition;
104 if ( startPos == nullPosition ) {
106 startPos = isRows ? mapToCache( *start, 0 ) : mapToCache( 0, *start );
107 endPos = isRows ? mapToCache( *end, 0 ) : mapToCache( 0, *end );
110 if ( startPos == nullPosition ) {
115 *start = isRows ? startPos.row : startPos.column;
116 *end = isRows ? endPos.row : endPos.column;
120 void CartesianDiagramDataCompressor::slotRowsAboutToBeInserted(
const QModelIndex& parent,
int start,
int end )
122 if ( !prepareDataChange( parent,
true, &start, &end ) ) {
125 for (
int i = 0; i < m_data.size(); ++i )
127 Q_ASSERT( start >= 0 && start <= m_data[ i ].size() );
128 m_data[ i ].insert( start, end - start + 1, DataPoint() );
132 void CartesianDiagramDataCompressor::slotRowsInserted(
const QModelIndex& parent,
int start,
int end )
134 if ( !prepareDataChange( parent,
true, &start, &end ) ) {
137 for (
int i = 0; i < m_data.size(); ++i )
139 for (
int j = start; j < m_data[i].size(); ++j ) {
140 retrieveModelData( CachePosition( j, i ) );
145 void CartesianDiagramDataCompressor::slotColumnsAboutToBeInserted(
const QModelIndex& parent,
int start,
int end )
147 if ( !prepareDataChange( parent,
false, &start, &end ) ) {
150 const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution );
151 Q_ASSERT( start >= 0 && start <= m_data.size() );
155 void CartesianDiagramDataCompressor::slotColumnsInserted(
const QModelIndex& parent,
int start,
int end )
157 if ( !prepareDataChange( parent,
false, &start, &end ) ) {
160 for (
int i = start; i < m_data.size(); ++i )
162 for (
int j = 0; j < m_data[i].size(); ++j ) {
163 retrieveModelData( CachePosition( j, i ) );
168 void CartesianDiagramDataCompressor::slotRowsAboutToBeRemoved(
const QModelIndex& parent,
int start,
int end )
170 if ( !prepareDataChange( parent,
true, &start, &end ) ) {
173 for (
int i = 0; i < m_data.size(); ++i ) {
174 m_data[ i ].remove( start, end - start + 1 );
178 void CartesianDiagramDataCompressor::slotRowsRemoved(
const QModelIndex& parent,
int start,
int end )
180 if ( parent != m_rootIndex )
182 Q_ASSERT( start <= end );
185 CachePosition startPos = mapToCache( start, 0 );
186 static const CachePosition nullPosition;
187 if ( startPos == nullPosition ) {
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 ) );
200 void CartesianDiagramDataCompressor::slotColumnsAboutToBeRemoved(
const QModelIndex& parent,
int start,
int end )
202 if ( !prepareDataChange( parent,
false, &start, &end ) ) {
205 m_data.remove( start, end - start + 1 );
208 void CartesianDiagramDataCompressor::slotColumnsRemoved(
const QModelIndex& parent,
int start,
int end )
210 if ( parent != m_rootIndex )
212 Q_ASSERT( start <= end );
215 const CachePosition startPos = mapToCache( 0, start );
217 static const CachePosition nullPosition;
218 if ( startPos == nullPosition ) {
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 ) );
231 void CartesianDiagramDataCompressor::slotModelHeaderDataChanged( Qt::Orientation orientation,
int first,
int last )
233 if ( orientation != Qt::Vertical )
236 if ( m_model->rowCount( m_rootIndex ) > 0 ) {
237 const QModelIndex firstRow = m_model->index( 0, first, m_rootIndex );
238 const QModelIndex lastRow = m_model->index( m_model->rowCount( m_rootIndex ) - 1, last, m_rootIndex );
240 slotModelDataChanged( firstRow, lastRow );
244 void CartesianDiagramDataCompressor::slotModelDataChanged(
245 const QModelIndex& topLeftIndex,
246 const QModelIndex& bottomRightIndex )
248 if ( topLeftIndex.parent() != m_rootIndex )
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 ) );
260 void CartesianDiagramDataCompressor::slotModelLayoutChanged()
263 calculateSampleStepWidth();
266 void CartesianDiagramDataCompressor::slotDiagramLayoutChanged(
AbstractDiagram* diagramBase )
275 int CartesianDiagramDataCompressor::modelDataColumns()
const 277 Q_ASSERT( m_datasetDimension != 0 );
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() );
289 int CartesianDiagramDataCompressor::modelDataRows()
const 292 if ( m_model && m_model->columnCount( m_rootIndex ) > 0 && m_xResolution > 0 ) {
293 return m_data.isEmpty() ? 0 : m_data.first().size();
299 void CartesianDiagramDataCompressor::setModel( QAbstractItemModel* model )
301 if ( model == m_model ) {
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() ) );
333 m_modelCache.setModel( 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() ) );
362 calculateSampleStepWidth();
365 void CartesianDiagramDataCompressor::setRootIndex(
const QModelIndex& root )
367 if ( m_rootIndex != root ) {
368 Q_ASSERT( root.model() == m_model || !root.isValid() );
370 m_modelCache.setRootIndex( root );
372 calculateSampleStepWidth();
376 void CartesianDiagramDataCompressor::recalcResolution()
378 setResolution( m_xResolution, m_yResolution );
381 void CartesianDiagramDataCompressor::setResolution(
int x,
int y )
383 if ( setResolutionInternal( x, y ) ) {
385 calculateSampleStepWidth();
389 bool CartesianDiagramDataCompressor::setResolutionInternal(
int x,
int y )
391 const int oldXRes = m_xResolution;
392 const int oldYRes = m_yResolution;
394 if ( m_datasetDimension != 1 ) {
396 m_xResolution = m_model ? m_model->rowCount( m_rootIndex ) : 0;
398 m_xResolution = qMax( 0, x );
400 m_yResolution = qMax( 0, y );
402 return m_xResolution != oldXRes || m_yResolution != oldYRes;
405 void CartesianDiagramDataCompressor::clearCache()
407 for (
int column = 0; column < m_data.size(); ++column )
408 m_data[column].fill( DataPoint() );
411 void CartesianDiagramDataCompressor::rebuildCache()
413 Q_ASSERT( m_datasetDimension != 0 );
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 );
425 m_dataValueAttributesCache.clear();
428 const CartesianDiagramDataCompressor::DataPoint& CartesianDiagramDataCompressor::data(
const CachePosition& position )
const 430 static DataPoint nullDataPoint;
431 if ( ! mapsToModelIndex( position ) ) {
432 return nullDataPoint;
434 if ( ! isCached( position ) ) {
435 retrieveModelData( position );
437 return m_data[ position.column ][ position.row ];
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();
448 for (
int column = 0; column < colCount; ++column )
450 const DataPointVector& data = m_data[ column ];
452 for ( DataPointVector::const_iterator it = data.begin(); it != data.end(); ++it, ++row )
454 const DataPoint& p = *it;
455 if ( !p.index.isValid() )
456 retrieveModelData( CachePosition( row, column ) );
458 if ( ISNAN( p.key ) || ISNAN( p.value ) ) {
462 if ( ISNAN( xMin ) ) {
468 xMin = qMin( xMin, p.key );
469 xMax = qMax( xMax, p.key );
470 yMin = qMin( yMin, p.value );
471 yMax = qMax( yMax, p.value );
476 const QPointF bottomLeft( xMin, yMin );
477 const QPointF topRight( xMax, yMax );
478 return qMakePair( bottomLeft, topRight );
481 void CartesianDiagramDataCompressor::retrieveModelData(
const CachePosition& position )
const 483 Q_ASSERT( mapsToModelIndex( position ) );
485 result.hidden =
true;
490 const QModelIndexList indexes = mapToModel( position );
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 ) );
499 if ( indexes.isEmpty() ) {
502 result.value = std::numeric_limits< qreal >::quiet_NaN();
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;
509 result.key += index.row();
511 result.index = indexes.at( 0 );
512 result.key /= indexes.size();
513 result.value /= indexes.size();
516 Q_FOREACH(
const QModelIndex& index, indexes ) {
518 if ( m_model->data( index,
DataHiddenRole ).value<
bool>() ==
false ) {
519 result.hidden =
false;
528 m_data[ position.column ][ position.row ] = result;
529 Q_ASSERT( isCached( position ) );
532 CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache(
533 const QModelIndex& index )
const 535 Q_ASSERT( m_datasetDimension != 0 );
537 static const CachePosition nullPosition;
538 if ( !index.isValid() ) {
541 return mapToCache( index.row(), index.column() );
544 CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache(
545 int row,
int column )
const 547 Q_ASSERT( m_datasetDimension != 0 );
549 if ( m_data.size() == 0 || m_data[ 0 ].size() == 0 ) {
550 return mapToCache( QModelIndex() );
553 if ( indexesPerPixel() == 0 ) {
554 return mapToCache( QModelIndex() );
556 return CachePosition(
int( row / indexesPerPixel() ), column / m_datasetDimension );
559 QModelIndexList CartesianDiagramDataCompressor::mapToModel(
const CachePosition& position )
const 561 QModelIndexList indexes;
562 if ( !mapsToModelIndex( position ) ) {
566 Q_ASSERT( position.column < modelDataColumns() );
567 if ( m_datasetDimension == 2 ) {
568 indexes << m_model->index( position.row, position.column * 2, m_rootIndex );
569 indexes << m_model->index( position.row, position.column * 2 + 1, m_rootIndex );
573 const qreal ipp = indexesPerPixel();
574 const int baseRow = floor( position.row * ipp );
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() ) {
588 qreal CartesianDiagramDataCompressor::indexesPerPixel()
const 590 if ( !m_model || m_data.size() == 0 || m_data[ 0 ].size() == 0 ) {
593 return qreal( m_model->rowCount( m_rootIndex ) ) / qreal( m_data[ 0 ].size() );
596 bool CartesianDiagramDataCompressor::mapsToModelIndex(
const CachePosition& position )
const 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();
603 void CartesianDiagramDataCompressor::invalidate(
const CachePosition& position )
605 if ( mapsToModelIndex( position ) ) {
606 m_data[ position.column ][ position.row ] = DataPoint();
610 m_dataValueAttributesCache.remove( position );
614 bool CartesianDiagramDataCompressor::isCached(
const CachePosition& position )
const 616 Q_ASSERT( mapsToModelIndex( position ) );
617 const DataPoint& p = m_data[ position.column ][ position.row ];
618 return p.index.isValid();
621 void CartesianDiagramDataCompressor::calculateSampleStepWidth()
623 if ( m_mode == Precise ) {
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
637 const qreal WantedSamples = 17;
638 if ( WantedSamples > indexesPerPixel() ) {
642 for ( i = 0; SomePrimes[i] != 0; ++i ) {
643 if ( WantedSamples * SomePrimes[i+1] > indexesPerPixel() ) {
647 m_sampleStep = SomePrimes[i];
648 if ( SomePrimes[i] == 0 ) {
649 m_sampleStep = SomePrimes[i-1];
651 m_sampleStep = SomePrimes[i];
656 void CartesianDiagramDataCompressor::setDatasetDimension(
int dimension )
658 if ( dimension != m_datasetDimension ) {
659 m_datasetDimension = dimension;
661 calculateSampleStepWidth();
DataValueAttributes dataValueAttributes() const
Retrieve the DataValueAttributes specified globally.
Diagram attributes dealing with data value labels.
AbstractDiagram defines the interface for diagram classes.
AbstractDiagram * diagram()
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 ...