File indexing completed on 2024-12-15 04:02:30

0001 /*
0002  * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
0003  *
0004  * This file is part of the KD Chart library.
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "KChartCartesianDiagramDataCompressor_p.h"
0010 
0011 #include <QtDebug>
0012 #include <QAbstractItemModel>
0013 
0014 #include "KChartAbstractCartesianDiagram.h"
0015 #include "KChartMath_p.h"
0016 
0017 
0018 using namespace KChart;
0019 using namespace std;
0020 
0021 CartesianDiagramDataCompressor::CartesianDiagramDataCompressor( QObject* parent )
0022     : QObject( parent )
0023     , m_mode( Precise )
0024     , m_xResolution( 0 )
0025     , m_yResolution( 0 )
0026     , m_sampleStep( 0 )
0027     , m_datasetDimension( 1 )
0028 {
0029     calculateSampleStepWidth();
0030     m_data.resize( 0 );
0031 }
0032 
0033 static bool contains( const CartesianDiagramDataCompressor::AggregatedDataValueAttributes& aggregated,
0034                       const DataValueAttributes& attributes )
0035 {
0036     CartesianDiagramDataCompressor::AggregatedDataValueAttributes::const_iterator it = aggregated.constBegin();
0037     for ( ; it != aggregated.constEnd(); ++it ) {
0038         if ( it.value() == attributes ) {
0039             return true;
0040         }
0041     }
0042     return false;
0043 }
0044 
0045 CartesianDiagramDataCompressor::AggregatedDataValueAttributes CartesianDiagramDataCompressor::aggregatedAttrs(
0046         const AbstractDiagram* diagram,
0047         const QModelIndex & index,
0048         const CachePosition& position ) const
0049 {
0050     // return cached attrs, if any
0051     DataValueAttributesCache::const_iterator i = m_dataValueAttributesCache.constFind( position );
0052     if ( i != m_dataValueAttributesCache.constEnd() ) {
0053         return i.value();
0054     }
0055 
0056     // aggregate attributes from all indices in the same CachePosition as index
0057     CartesianDiagramDataCompressor::AggregatedDataValueAttributes aggregated;
0058     const auto neighborIndexes = mapToModel( position );
0059     for ( const QModelIndex& neighborIndex : neighborIndexes ) {
0060         DataValueAttributes attrs = diagram->dataValueAttributes( neighborIndex );
0061         // only store visible and unique attributes
0062         if ( !attrs.isVisible() ) {
0063             continue;
0064         }
0065         if ( !contains( aggregated, attrs ) ) {
0066             aggregated[ neighborIndex ] = attrs;
0067         }
0068     }
0069     // if none of the attributes had the visible flag set, we just take the one set for the index
0070     // to avoid returning an empty list (### why not return an empty list?)
0071     if ( aggregated.isEmpty() ) {
0072         aggregated[index] = diagram->dataValueAttributes( index );
0073     }
0074 
0075     m_dataValueAttributesCache[position] = aggregated;
0076     return aggregated;
0077 }
0078 
0079 bool CartesianDiagramDataCompressor::prepareDataChange( const QModelIndex& parent, bool isRows,
0080                                                         int* start, int* end )
0081 {
0082     if ( parent != m_rootIndex ) {
0083         return false;
0084     }
0085     Q_ASSERT( *start <= *end );
0086 
0087     CachePosition startPos = isRows ? mapToCache( *start, 0  ) : mapToCache( 0, *start );
0088     CachePosition endPos = isRows ? mapToCache( *end, 0  ) : mapToCache( 0, *end );
0089 
0090     static const CachePosition nullPosition;
0091     if ( startPos == nullPosition ) {
0092         rebuildCache();
0093         startPos = isRows ? mapToCache( *start, 0  ) : mapToCache( 0, *start );
0094         endPos = isRows ? mapToCache( *end, 0  ) : mapToCache( 0, *end );
0095         // The start position still isn't valid,
0096         // means that no resolution was set yet or we're about to add the first rows
0097         if ( startPos == nullPosition ) {
0098             return false;
0099         }
0100     }
0101 
0102     *start = isRows ? startPos.row : startPos.column;
0103     *end = isRows ? endPos.row : endPos.column;
0104     return true;
0105 }
0106 
0107 void CartesianDiagramDataCompressor::slotRowsAboutToBeInserted( const QModelIndex& parent, int start, int end )
0108 {
0109     if ( !prepareDataChange( parent, true, &start, &end ) ) {
0110         return;
0111     }
0112     for ( int i = 0; i < m_data.size(); ++i )
0113     {
0114         Q_ASSERT( start >= 0 && start <= m_data[ i ].size() );
0115         m_data[ i ].insert( start, end - start + 1, DataPoint() );
0116     }
0117 }
0118 
0119 void CartesianDiagramDataCompressor::slotRowsInserted( const QModelIndex& parent, int start, int end )
0120 {
0121     if ( !prepareDataChange( parent, true, &start, &end ) ) {
0122         return;
0123     }
0124     for ( int i = 0; i < m_data.size(); ++i )
0125     {
0126         for ( int j = start; j < m_data[i].size(); ++j ) {
0127             retrieveModelData( CachePosition( j, i ) );
0128         }
0129     }
0130 }
0131 
0132 void CartesianDiagramDataCompressor::slotColumnsAboutToBeInserted( const QModelIndex& parent, int start, int end )
0133 {
0134     if ( !prepareDataChange( parent, false, &start, &end ) ) {
0135         return;
0136     }
0137     const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution );
0138     Q_ASSERT( start >= 0 && start <= m_data.size() );
0139     m_data.insert( start, end - start + 1, QVector< DataPoint >( rowCount ) );
0140 }
0141 
0142 void CartesianDiagramDataCompressor::slotColumnsInserted( const QModelIndex& parent, int start, int end )
0143 {
0144     if ( !prepareDataChange( parent, false, &start, &end ) ) {
0145         return;
0146     }
0147     for ( int i = start; i < m_data.size(); ++i )
0148     {
0149         for (int j = 0; j < m_data[i].size(); ++j ) {
0150             retrieveModelData( CachePosition( j, i ) );
0151         }
0152     }
0153 }
0154 
0155 void CartesianDiagramDataCompressor::slotRowsAboutToBeRemoved( const QModelIndex& parent, int start, int end )
0156 {
0157     if ( !prepareDataChange( parent, true, &start, &end ) ) {
0158         return;
0159     }
0160     for ( int i = 0; i < m_data.size(); ++i ) {
0161         m_data[ i ].remove( start, end - start + 1 );
0162     }
0163 }
0164 
0165 void CartesianDiagramDataCompressor::slotRowsRemoved( const QModelIndex& parent, int start, int end )
0166 {
0167     if ( parent != m_rootIndex )
0168         return;
0169     Q_ASSERT( start <= end );
0170     Q_UNUSED( end )
0171 
0172     CachePosition startPos = mapToCache( start, 0 );
0173     static const CachePosition nullPosition;
0174     if ( startPos == nullPosition ) {
0175         // Since we should already have rebuilt the cache, it won't help to rebuild it again.
0176         // Do not Q_ASSERT() though, since the resolution might simply not be set or we might now have 0 rows
0177         return;
0178     }
0179 
0180     for ( int i = 0; i < m_data.size(); ++i ) {
0181         for (int j = startPos.row; j < m_data[i].size(); ++j ) {
0182             retrieveModelData( CachePosition( j, i ) );
0183         }
0184     }
0185 }
0186 
0187 void CartesianDiagramDataCompressor::slotColumnsAboutToBeRemoved( const QModelIndex& parent, int start, int end )
0188 {
0189     if ( !prepareDataChange( parent, false, &start, &end ) ) {
0190         return;
0191     }
0192     m_data.remove( start, end - start + 1 );
0193 }
0194 
0195 void CartesianDiagramDataCompressor::slotColumnsRemoved( const QModelIndex& parent, int start, int end )
0196 {
0197     if ( parent != m_rootIndex )
0198         return;
0199     Q_ASSERT( start <= end );
0200     Q_UNUSED( end );
0201 
0202     const CachePosition startPos = mapToCache( 0, start );
0203 
0204     static const CachePosition nullPosition;
0205     if ( startPos == nullPosition ) {
0206         // Since we should already have rebuilt the cache, it won't help to rebuild it again.
0207         // Do not Q_ASSERT() though, since the resolution might simply not be set or we might now have 0 columns
0208         return;
0209     }
0210 
0211     for ( int i = startPos.column; i < m_data.size(); ++i ) {
0212         for ( int j = 0; j < m_data[i].size(); ++j ) {
0213             retrieveModelData( CachePosition( j, i ) );
0214         }
0215     }
0216 }
0217 
0218 void CartesianDiagramDataCompressor::slotModelHeaderDataChanged( Qt::Orientation orientation, int first, int last )
0219 {
0220     if ( orientation != Qt::Vertical )
0221         return;
0222 
0223     if ( m_model->rowCount( m_rootIndex ) > 0 ) {
0224         const QModelIndex firstRow = m_model->index( 0, first, m_rootIndex ); // checked
0225         const QModelIndex lastRow = m_model->index( m_model->rowCount( m_rootIndex ) - 1, last, m_rootIndex ); // checked
0226 
0227         slotModelDataChanged( firstRow, lastRow );
0228     }
0229 }
0230 
0231 void CartesianDiagramDataCompressor::slotModelDataChanged(
0232     const QModelIndex& topLeftIndex,
0233     const QModelIndex& bottomRightIndex )
0234 {
0235     if ( topLeftIndex.parent() != m_rootIndex )
0236         return;
0237     Q_ASSERT( topLeftIndex.parent() == bottomRightIndex.parent() );
0238     Q_ASSERT( topLeftIndex.row() <= bottomRightIndex.row() );
0239     Q_ASSERT( topLeftIndex.column() <= bottomRightIndex.column() );
0240     CachePosition topleft = mapToCache( topLeftIndex );
0241     CachePosition bottomright = mapToCache( bottomRightIndex );
0242     for ( int row = topleft.row; row <= bottomright.row; ++row )
0243         for ( int column = topleft.column; column <= bottomright.column; ++column )
0244             invalidate( CachePosition( row, column ) );
0245 }
0246 
0247 void CartesianDiagramDataCompressor::slotModelLayoutChanged()
0248 {
0249     rebuildCache();
0250     calculateSampleStepWidth();
0251 }
0252 
0253 void CartesianDiagramDataCompressor::slotDiagramLayoutChanged( AbstractDiagram* diagramBase )
0254 {
0255     AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* >( diagramBase );
0256     Q_ASSERT( diagram );
0257     if ( diagram->datasetDimension() != m_datasetDimension ) {
0258         setDatasetDimension( diagram->datasetDimension() );
0259     }
0260 }
0261 
0262 int CartesianDiagramDataCompressor::modelDataColumns() const
0263 {
0264     Q_ASSERT( m_datasetDimension != 0 );
0265     // only operational if there is a model and a resolution
0266     if ( m_model ) {
0267         const int effectiveDimension = m_datasetDimension == 2 ? 2 : 1;
0268         const int columns = m_model->columnCount( m_rootIndex ) / effectiveDimension;
0269         Q_ASSERT( columns == m_data.size() );
0270         return columns;
0271     } else {
0272         return 0;
0273     }
0274 }
0275 
0276 int CartesianDiagramDataCompressor::modelDataRows() const
0277 {
0278     // only operational if there is a model, columns, and a resolution
0279     if ( m_model && m_model->columnCount( m_rootIndex ) > 0 && m_xResolution > 0 ) {
0280         return m_data.isEmpty() ? 0 : m_data.first().size();
0281     } else {
0282         return 0;
0283     }
0284 }
0285 
0286 void CartesianDiagramDataCompressor::setModel( QAbstractItemModel* model )
0287 {
0288     if ( model == m_model ) {
0289         return;
0290     }
0291 
0292     if ( m_model != nullptr ) {
0293         disconnect( m_model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
0294                  this, SLOT(slotModelHeaderDataChanged(Qt::Orientation,int,int)) );
0295         disconnect( m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
0296                  this, SLOT(slotModelDataChanged(QModelIndex,QModelIndex)) );
0297         disconnect( m_model, SIGNAL(layoutChanged()),
0298                  this, SLOT(slotModelLayoutChanged()) );
0299         disconnect( m_model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
0300                  this, SLOT(slotRowsAboutToBeInserted(QModelIndex,int,int)) );
0301         disconnect( m_model, SIGNAL(rowsInserted(QModelIndex,int,int)),
0302                  this, SLOT(slotRowsInserted(QModelIndex,int,int)) );
0303         disconnect( m_model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
0304                  this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)) );
0305         disconnect( m_model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
0306                  this, SLOT(slotRowsRemoved(QModelIndex,int,int)) );
0307         disconnect( m_model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)),
0308                  this, SLOT(slotColumnsAboutToBeInserted(QModelIndex,int,int)) );
0309         disconnect( m_model, SIGNAL(columnsInserted(QModelIndex,int,int)),
0310                  this, SLOT(slotColumnsInserted(QModelIndex,int,int)) );
0311         disconnect( m_model, SIGNAL(columnsRemoved(QModelIndex,int,int)),
0312                  this, SLOT(slotColumnsRemoved(QModelIndex,int,int)) );
0313         disconnect( m_model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)),
0314                  this, SLOT(slotColumnsAboutToBeRemoved(QModelIndex,int,int)) );
0315         disconnect( m_model, SIGNAL(modelReset()),
0316                     this, SLOT(rebuildCache()) );
0317         m_model = nullptr;
0318     }
0319 
0320     m_modelCache.setModel( model );
0321 
0322     if ( model != nullptr ) {
0323         m_model = model;
0324         connect( m_model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
0325                  SLOT(slotModelHeaderDataChanged(Qt::Orientation,int,int)) );
0326         connect( m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
0327                  SLOT(slotModelDataChanged(QModelIndex,QModelIndex)) );
0328         connect( m_model, SIGNAL(layoutChanged()),
0329                  SLOT(slotModelLayoutChanged()) );
0330         connect( m_model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
0331                  SLOT(slotRowsAboutToBeInserted(QModelIndex,int,int)) );
0332         connect( m_model, SIGNAL(rowsInserted(QModelIndex,int,int)),
0333                  SLOT(slotRowsInserted(QModelIndex,int,int)) );
0334         connect( m_model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
0335                  SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)) );
0336         connect( m_model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
0337                  SLOT(slotRowsRemoved(QModelIndex,int,int)) );
0338         connect( m_model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)),
0339                  SLOT(slotColumnsAboutToBeInserted(QModelIndex,int,int)) );
0340         connect( m_model, SIGNAL(columnsInserted(QModelIndex,int,int)),
0341                  SLOT(slotColumnsInserted(QModelIndex,int,int)) );
0342         connect( m_model, SIGNAL(columnsRemoved(QModelIndex,int,int)),
0343                  SLOT(slotColumnsRemoved(QModelIndex,int,int)) );
0344         connect( m_model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)),
0345                  SLOT(slotColumnsAboutToBeRemoved(QModelIndex,int,int)) );
0346         connect( m_model, SIGNAL(modelReset()), SLOT(rebuildCache()) );
0347     }
0348     rebuildCache();
0349     calculateSampleStepWidth();
0350 }
0351 
0352 void CartesianDiagramDataCompressor::setRootIndex( const QModelIndex& root )
0353 {
0354     if ( m_rootIndex != root ) {
0355         Q_ASSERT( root.model() == m_model || !root.isValid() );
0356         m_rootIndex = root;
0357         m_modelCache.setRootIndex( root );
0358         rebuildCache();
0359         calculateSampleStepWidth();
0360     }
0361 }
0362 
0363 void CartesianDiagramDataCompressor::recalcResolution()
0364 {
0365     setResolution( m_xResolution, m_yResolution );
0366 }
0367 
0368 void CartesianDiagramDataCompressor::setResolution( int x, int y )
0369 {
0370     if ( setResolutionInternal( x, y ) ) {
0371         rebuildCache();
0372         calculateSampleStepWidth();
0373     }
0374 }
0375 
0376 bool CartesianDiagramDataCompressor::setResolutionInternal( int x, int y )
0377 {
0378     const int oldXRes = m_xResolution;
0379     const int oldYRes = m_yResolution;
0380 
0381     if ( m_datasetDimension != 1 ) {
0382         // just ignore the X resolution in that case
0383         m_xResolution = m_model ? m_model->rowCount( m_rootIndex ) : 0;
0384     } else {
0385         m_xResolution = qMax( 0, x );
0386     }
0387     m_yResolution = qMax( 0, y );
0388 
0389     return m_xResolution != oldXRes || m_yResolution != oldYRes;
0390 }
0391 
0392 void CartesianDiagramDataCompressor::clearCache()
0393 {
0394     for ( int column = 0; column < m_data.size(); ++column )
0395         m_data[column].fill( DataPoint() );
0396 }
0397 
0398 void CartesianDiagramDataCompressor::rebuildCache()
0399 {
0400     Q_ASSERT( m_datasetDimension != 0 );
0401 
0402     m_data.clear();
0403     setResolutionInternal( m_xResolution, m_yResolution );
0404     const int columnDivisor = m_datasetDimension == 2 ? 2 : 1;
0405     const int columnCount = m_model ? m_model->columnCount( m_rootIndex ) / columnDivisor : 0;
0406     const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution );
0407     m_data.resize( columnCount );
0408     for ( int i = 0; i < columnCount; ++i ) {
0409         m_data[i].resize( rowCount );
0410     }
0411     // also empty the attrs cache
0412     m_dataValueAttributesCache.clear();
0413 }
0414 
0415 const CartesianDiagramDataCompressor::DataPoint& CartesianDiagramDataCompressor::data( const CachePosition& position ) const
0416 {
0417     static DataPoint nullDataPoint;
0418     if ( ! mapsToModelIndex( position ) ) {
0419         return nullDataPoint;
0420     }
0421     if ( ! isCached( position ) ) {
0422         retrieveModelData( position );
0423     }
0424     return m_data.at( position.column ).at( position.row );
0425 }
0426 
0427 QPair< QPointF, QPointF > CartesianDiagramDataCompressor::dataBoundaries() const
0428 {
0429     const int colCount = modelDataColumns();
0430     qreal xMin = std::numeric_limits< qreal >::quiet_NaN();
0431     qreal xMax = std::numeric_limits< qreal >::quiet_NaN();
0432     qreal yMin = std::numeric_limits< qreal >::quiet_NaN();
0433     qreal yMax = std::numeric_limits< qreal >::quiet_NaN();
0434 
0435     for ( int column = 0; column < colCount; ++column )
0436     {
0437         const DataPointVector& data = m_data.at( column );
0438         int row = 0;
0439         for ( DataPointVector::const_iterator it = data.begin(); it != data.end(); ++it, ++row )
0440         {
0441             const DataPoint& p = *it;
0442             if ( !p.index.isValid() )
0443                 retrieveModelData( CachePosition( row, column ) );
0444 
0445             if ( ISNAN( p.key ) || ISNAN( p.value ) ) {
0446                 continue;
0447             }
0448 
0449             if ( ISNAN( xMin ) ) {
0450                 xMin = p.key;
0451                 xMax = p.key;
0452                 yMin = p.value;
0453                 yMax = p.value;
0454             } else {
0455                 xMin = qMin( xMin, p.key );
0456                 xMax = qMax( xMax, p.key );
0457                 yMin = qMin( yMin, p.value );
0458                 yMax = qMax( yMax, p.value );
0459             }
0460         }
0461     }
0462 
0463     const QPointF bottomLeft( xMin, yMin );
0464     const QPointF topRight( xMax, yMax );
0465     return qMakePair( bottomLeft, topRight );
0466 }
0467 
0468 void CartesianDiagramDataCompressor::retrieveModelData( const CachePosition& position ) const
0469 {
0470     Q_ASSERT( mapsToModelIndex( position ) );
0471     DataPoint result;
0472     result.hidden = true;
0473 
0474     switch ( m_mode ) {
0475     case Precise:
0476     {
0477         const QModelIndexList indexes = mapToModel( position );
0478 
0479         if ( m_datasetDimension == 2 ) {
0480             Q_ASSERT( indexes.count() == 2 );
0481             const QModelIndex& xIndex = indexes.at( 0 );
0482             result.index = xIndex;
0483             result.key = m_modelCache.data( xIndex );
0484             result.value = m_modelCache.data( indexes.at( 1 ) );
0485         } else {
0486             if ( indexes.isEmpty() ) {
0487                 break;
0488             }
0489             result.value = std::numeric_limits< qreal >::quiet_NaN();
0490             result.key = 0.0;
0491             for ( const QModelIndex& index : indexes ) {
0492                 const qreal value = m_modelCache.data( index );
0493                 if ( !ISNAN( value ) ) {
0494                     result.value = ISNAN( result.value ) ? value : result.value + value;
0495                 }
0496                 result.key += index.row();
0497             }
0498             result.index = indexes.at( 0 );
0499             result.key /= indexes.size();
0500             result.value /= indexes.size();
0501         }
0502 
0503         for ( const QModelIndex& index : indexes ) {
0504             // the DataPoint point is visible if any of the underlying, aggregated points is visible
0505             if ( m_model->data( index, DataHiddenRole ).value<bool>() == false ) {
0506                 result.hidden = false;
0507             }
0508         }
0509         break;
0510     }
0511     case SamplingSeven:
0512         break;
0513     }
0514 
0515     m_data[ position.column ][ position.row ] = result;
0516     Q_ASSERT( isCached( position ) );
0517 }
0518 
0519 CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache(
0520         const QModelIndex& index ) const
0521 {
0522     Q_ASSERT( m_datasetDimension != 0 );
0523 
0524     static const CachePosition nullPosition;
0525     if ( !index.isValid() ) {
0526         return nullPosition;
0527     }
0528     return mapToCache( index.row(), index.column() );
0529 }
0530 
0531 CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache(
0532         int row, int column ) const
0533 {
0534     Q_ASSERT( m_datasetDimension != 0 );
0535 
0536     if ( m_data.size() == 0 || m_data.at( 0 ).size() == 0 ) {
0537         return mapToCache( QModelIndex() );
0538     }
0539     // assumption: indexes per column == 1
0540     if ( indexesPerPixel() == 0 ) {
0541         return mapToCache( QModelIndex() );
0542     }
0543     return CachePosition( int( row / indexesPerPixel() ), column / m_datasetDimension );
0544 }
0545 
0546 QModelIndexList CartesianDiagramDataCompressor::mapToModel( const CachePosition& position ) const
0547 {
0548     QModelIndexList indexes;
0549     if ( !mapsToModelIndex( position ) ) {
0550         return indexes;
0551     }
0552 
0553     Q_ASSERT( position.column < modelDataColumns() );
0554     if ( m_datasetDimension == 2 ) {
0555         indexes << m_model->index( position.row, position.column * 2, m_rootIndex ); // checked
0556         indexes << m_model->index( position.row, position.column * 2 + 1, m_rootIndex ); // checked
0557     } else {
0558         // here, indexes per column is usually but not always 1 (e.g. stock diagrams can have three
0559         // or four dimensions: High-Low-Close or Open-High-Low-Close)
0560         const qreal ipp = indexesPerPixel();
0561         const int baseRow = floor( position.row * ipp );
0562         // the following line needs to work for the last row(s), too...
0563         const int endRow = floor( ( position.row + 1 ) * ipp );
0564         for ( int row = baseRow; row < endRow; ++row ) {
0565             Q_ASSERT( row < m_model->rowCount( m_rootIndex ) );
0566             const QModelIndex index = m_model->index( row, position.column, m_rootIndex );
0567             if ( index.isValid() ) {
0568                 indexes << index;
0569             }
0570         }
0571     }
0572     return indexes;
0573 }
0574 
0575 qreal CartesianDiagramDataCompressor::indexesPerPixel() const
0576 {
0577     if ( !m_model || m_data.size() == 0 || m_data.at( 0 ).size() == 0 )  {
0578         return 0;
0579     }
0580     return qreal( m_model->rowCount( m_rootIndex ) ) / qreal( m_data.at( 0 ).size() );
0581 }
0582 
0583 bool CartesianDiagramDataCompressor::mapsToModelIndex( const CachePosition& position ) const
0584 {
0585     return m_model && m_data.size() > 0 && m_data.at( 0 ).size() > 0 &&
0586            position.column >= 0 && position.column < m_data.size() &&
0587            position.row >=0 && position.row < m_data.at( 0 ).size();
0588 }
0589 
0590 void CartesianDiagramDataCompressor::invalidate( const CachePosition& position )
0591 {
0592     if ( mapsToModelIndex( position ) ) {
0593         m_data[ position.column ][ position.row ] = DataPoint();
0594         // Also invalidate the data value attributes at "position".
0595         // Otherwise the user overwrites the attributes without us noticing
0596         // it because we keep reading what's in the cache.
0597         m_dataValueAttributesCache.remove( position );
0598     }
0599 }
0600 
0601 bool CartesianDiagramDataCompressor::isCached( const CachePosition& position ) const
0602 {
0603     Q_ASSERT( mapsToModelIndex( position ) );
0604     const DataPoint& p = m_data.at( position.column ).at( position.row );
0605     return p.index.isValid();
0606 }
0607 
0608 void CartesianDiagramDataCompressor::calculateSampleStepWidth()
0609 {
0610     if ( m_mode == Precise ) {
0611         m_sampleStep = 1;
0612         return;
0613     }
0614 
0615     static unsigned int SomePrimes[] = {
0616         2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47,
0617         53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101,
0618         151, 211, 313, 401, 503, 607, 701, 811, 911, 1009,
0619         10037, 12911, 16001, 20011, 50021,
0620         100003, 137867, 199999, 500009, 707753, 1000003, 0
0621     }; // ... after that, having a model at all becomes impractical
0622 
0623     // we want at least 17 samples per data point, using a prime step width
0624     const qreal WantedSamples = 17;
0625     if ( WantedSamples > indexesPerPixel() ) {
0626         m_sampleStep = 1;
0627     } else {
0628         int i;
0629         for ( i = 0; SomePrimes[i] != 0; ++i ) {
0630             if ( WantedSamples * SomePrimes[i+1] > indexesPerPixel() ) {
0631                 break;
0632             }
0633         }
0634         m_sampleStep = SomePrimes[i];
0635         if ( SomePrimes[i] == 0 ) {
0636             m_sampleStep = SomePrimes[i-1];
0637         } else {
0638             m_sampleStep = SomePrimes[i];
0639         }
0640     }
0641 }
0642 
0643 void CartesianDiagramDataCompressor::setDatasetDimension( int dimension )
0644 {
0645     if ( dimension != m_datasetDimension ) {
0646         m_datasetDimension = dimension;
0647         rebuildCache();
0648         calculateSampleStepWidth();
0649     }
0650 }