KDChartCartesianDiagramDataCompressor_p.cpp

Go to the documentation of this file.
00001 /****************************************************************************
00002 ** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB.  All rights reserved.
00003 **
00004 ** This file is part of the KD Chart library.
00005 **
00006 ** Licensees holding valid commercial KD Chart licenses may use this file in
00007 ** accordance with the KD Chart Commercial License Agreement provided with
00008 ** the Software.
00009 **
00010 **
00011 ** This file may be distributed and/or modified under the terms of the
00012 ** GNU General Public License version 2 and version 3 as published by the
00013 ** Free Software Foundation and appearing in the file LICENSE.GPL included.
00014 **
00015 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
00016 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00017 **
00018 ** Contact info@kdab.com if any conditions of this licensing are not
00019 ** clear to you.
00020 **
00021 **********************************************************************/
00022 
00023 #include "KDChartCartesianDiagramDataCompressor_p.h"
00024 
00025 #include <QtDebug>
00026 #include <QAbstractItemModel>
00027 
00028 #include "KDChartAbstractCartesianDiagram.h"
00029 
00030 #include <KDABLibFakes>
00031 
00032 using namespace KDChart;
00033 using namespace std;
00034 
00035 CartesianDiagramDataCompressor::CartesianDiagramDataCompressor( QObject* parent )
00036     : QObject( parent )
00037     , m_mode( Precise )
00038     , m_xResolution( 0 )
00039     , m_yResolution( 0 )
00040     , m_sampleStep( 0 )
00041     , m_datasetDimension( 1 )
00042 {
00043     calculateSampleStepWidth();
00044 }
00045 
00046 QModelIndexList CartesianDiagramDataCompressor::indexesAt( const CachePosition& position ) const
00047 {
00048     if ( isValidCachePosition( position ) ) {
00049         CachePosition posPrev( position );
00050         if( m_datasetDimension == 2 ){
00051             if(posPrev.second)
00052                 --posPrev.second;
00053         }else{
00054             if(posPrev.first)
00055                 --posPrev.first;
00056         }
00057         const QModelIndexList indPrev = mapToModel( posPrev );
00058         const QModelIndexList indCur  = mapToModel( position );
00059 
00060         QModelIndexList indexes;
00061         if( m_datasetDimension == 2 )
00062         {
00063             const int iStart = (indPrev.empty() || indPrev==indCur) ? indCur.first().column()
00064                              : indPrev.first().column() + 1;
00065             const int iEnd   = indCur.last().column();
00066             for( int i=iStart; i<=iEnd; ++i){
00067                 indexes << m_model->index( position.first, i, m_rootIndex );
00068             }
00069         }
00070         else
00071         {
00072             const int iStart = (indPrev.empty() || indPrev==indCur)  ? indCur.first().row()
00073                              : indPrev.first().row() + 1;
00074             const int iEnd   = (indCur.isEmpty()) ? iStart : indCur.first().row();
00075             //qDebug()<<iStart<<iEnd << iEnd-iStart;
00076             for( int i=iStart; i<=iEnd; ++i){
00077                 indexes << m_model->index( i, position.second, m_rootIndex );
00078             }
00079         }
00080         return indexes;
00081     } else {
00082         return QModelIndexList();
00083     }
00084 }
00085 
00086 
00087 CartesianDiagramDataCompressor::DataValueAttributesList CartesianDiagramDataCompressor::aggregatedAttrs(
00088         AbstractDiagram * diagram,
00089         const QModelIndex & index,
00090         const CachePosition& position ) const
00091 {
00092     // return cached attrs, if any
00093     DataValueAttributesCache::const_iterator i = m_dataValueAttributesCache.constFind(position);
00094     if( i != m_dataValueAttributesCache.constEnd() )
00095         return i.value();
00096     // retrieve attrs from all cells between the prev. cell and the current one
00097     CartesianDiagramDataCompressor::DataValueAttributesList allAttrs;
00098     const QModelIndexList indexes( indexesAt( position ) );
00099     KDAB_FOREACH( QModelIndex idx, indexes ) {
00100         DataValueAttributes attrs( diagram->dataValueAttributes( idx ) );
00101         if( attrs.isVisible() ){
00102             // make sure no duplicate attrs are stored
00103             bool isDuplicate = false;
00104             CartesianDiagramDataCompressor::DataValueAttributesList::const_iterator i = allAttrs.constBegin();
00105             while (i != allAttrs.constEnd()) {
00106                 if( i.value() == attrs ){
00107                     isDuplicate = true;
00108                     continue;
00109                 }
00110                 ++i;
00111             }
00112             if( !isDuplicate ){
00113                 //qDebug()<<idx.row();
00114                 allAttrs[idx] = attrs;
00115             }
00116         }
00117     }
00118     // if none of the attrs had the visible flag set
00119     // we just take the one set for the index to not return an empty list
00120     if( allAttrs.empty() ){
00121         allAttrs[index] = diagram->dataValueAttributes( index );
00122     }
00123     // cache the attrs
00124     m_dataValueAttributesCache[position] = allAttrs;
00125     return allAttrs;
00126 }
00127 
00128 
00129 void CartesianDiagramDataCompressor::slotRowsAboutToBeInserted( const QModelIndex& parent, int start, int end )
00130 {
00131     if ( parent != m_rootIndex )
00132         return;
00133     Q_ASSERT( start <= end );
00134 
00135     CachePosition startPos = mapToCache( start, 0 );
00136     CachePosition endPos = mapToCache( end, 0 );
00137 
00138     static const CachePosition NullPosition( -1, -1 );
00139     if( startPos == NullPosition )
00140     {
00141         rebuildCache();
00142         startPos = mapToCache( start, 0 );
00143         endPos = mapToCache( end, 0 );
00144         // The start position still isn't valid,
00145         // means that no resolution was set yet or we're about to add the first rows
00146         if( startPos == NullPosition ) {
00147             return;
00148         }
00149     }
00150 
00151     start = startPos.first;
00152     end = endPos.first;
00153 
00154     for( int i = 0; i < m_data.size(); ++i )
00155     {
00156         Q_ASSERT( start >= 0 && start <= m_data[ i ].size() );
00157         m_data[ i ].insert( start, end - start + 1, DataPoint() );
00158     }
00159 }
00160 
00161 void CartesianDiagramDataCompressor::slotRowsInserted( const QModelIndex& parent, int start, int end )
00162 {
00163     if ( parent != m_rootIndex )
00164         return;
00165     Q_ASSERT( start <= end );
00166 
00167     CachePosition startPos = mapToCache( start, 0 );
00168     CachePosition endPos = mapToCache( end, 0 );
00169 
00170     static const CachePosition NullPosition( -1, -1 );
00171     if( startPos == NullPosition )
00172     {
00173         // Rebuild the cache at this point if we have added the first rows
00174         rebuildCache();
00175         startPos = mapToCache( start, 0 );
00176         endPos = mapToCache( end, 0 );
00177         // The start position still isn't valid,
00178         // means that no resolution was set yet
00179         if( startPos == NullPosition ) {
00180             return;
00181         }
00182     }
00183 
00184     start = startPos.first;
00185     end = endPos.first;
00186 
00187     for( int i = 0; i < m_data.size(); ++i )
00188     {
00189         for( int j = start; j < m_data[i].size(); ++j ) {
00190             retrieveModelData( CachePosition( j, i ) );
00191         }
00192     }
00193 }
00194 
00195 void CartesianDiagramDataCompressor::slotColumnsAboutToBeInserted( const QModelIndex& parent, int start, int end )
00196 {
00197     if ( parent != m_rootIndex )
00198         return;
00199     Q_ASSERT( start <= end );
00200 
00201     CachePosition startPos = mapToCache( 0, start );
00202     CachePosition endPos = mapToCache( 0, end );
00203 
00204     static const CachePosition NullPosition( -1, -1 );
00205     if( startPos == NullPosition )
00206     {
00207         rebuildCache();
00208         startPos = mapToCache( 0, start );
00209         endPos = mapToCache( 0, end );
00210         // The start position still isn't valid,
00211         // means that no resolution was set yet or we're about to add the first columns
00212         if( startPos == NullPosition ) {
00213             return;
00214         }
00215     }
00216 
00217     start = startPos.second;
00218     end = endPos.second;
00219 
00220     const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution );
00221     Q_ASSERT( start >= 0 && start <= m_data.size() );
00222     m_data.insert( start, end - start + 1, QVector< DataPoint >( rowCount ) );
00223 }
00224 
00225 void CartesianDiagramDataCompressor::slotColumnsInserted( const QModelIndex& parent, int start, int end )
00226 {
00227     if ( parent != m_rootIndex )
00228         return;
00229     Q_ASSERT( start <= end );
00230 
00231     CachePosition startPos = mapToCache( 0, start );
00232     CachePosition endPos = mapToCache( 0, end );
00233 
00234     static const CachePosition NullPosition( -1, -1 );
00235     if( startPos == NullPosition )
00236     {
00237         // Rebuild the cache at this point if we have added the first columns
00238         rebuildCache();
00239         startPos = mapToCache( 0, start );
00240         endPos = mapToCache( 0, end );
00241         // The start position still isn't valid,
00242         // means that no resolution was set yet
00243         if( startPos == NullPosition ) {
00244             return;
00245         }
00246     }
00247 
00248     start = startPos.second;
00249     end = endPos.second;
00250 
00251     for( int i = start; i < m_data.size(); ++i )
00252     {
00253         for(int j = 0; j < m_data[i].size(); ++j ) {
00254             retrieveModelData( CachePosition( j, i ) );
00255         }
00256     }
00257 }
00258 
00259 void CartesianDiagramDataCompressor::slotRowsAboutToBeRemoved( const QModelIndex& parent, int start, int end )
00260 {
00261     if ( parent != m_rootIndex )
00262         return;
00263     Q_ASSERT( start <= end );
00264 
00265     CachePosition startPos = mapToCache( start, 0 );
00266     CachePosition endPos = mapToCache( end, 0 );
00267 
00268     static const CachePosition NullPosition( -1, -1 );
00269     if( startPos == NullPosition )
00270     {
00271         rebuildCache();
00272         startPos = mapToCache( start, 0 );
00273         endPos = mapToCache( end, 0 );
00274         // The start position still isn't valid,
00275         // probably means that no resolution was set yet
00276         if( startPos == NullPosition ) {
00277             return;
00278         }
00279     }
00280 
00281     start = startPos.first;
00282     end = endPos.first;
00283 
00284     for( int i = 0; i < m_data.size(); ++i )
00285     {
00286         m_data[ i ].remove( start, end - start + 1 );
00287     }
00288 }
00289 
00290 void CartesianDiagramDataCompressor::slotRowsRemoved( const QModelIndex& parent, int start, int end )
00291 {
00292     if ( parent != m_rootIndex )
00293         return;
00294     Q_ASSERT( start <= end );
00295 
00296     CachePosition startPos = mapToCache( start, 0 );
00297     CachePosition endPos = mapToCache( end, 0 );
00298 
00299     start = startPos.first;
00300     end = endPos.first;
00301 
00302     static const CachePosition NullPosition( -1, -1 );
00303     if( startPos == NullPosition )
00304     {
00305         // Since we should already have rebuilt the cache, it won't help to rebuild it again.
00306         // Do not Q_ASSERT() though, since the resolution might simply not be set or we might now have 0 rows
00307         return;
00308     }
00309 
00310     for( int i = 0; i < m_data.size(); ++i ) {
00311         for(int j = start; j < m_data[i].size(); ++j ) {
00312             retrieveModelData( CachePosition( j, i ) );
00313         }
00314     }
00315 }
00316 
00317 void CartesianDiagramDataCompressor::slotColumnsAboutToBeRemoved( const QModelIndex& parent, int start, int end )
00318 {
00319     if ( parent != m_rootIndex )
00320         return;
00321     Q_ASSERT( start <= end );
00322 
00323     CachePosition startPos = mapToCache( 0, start );
00324     CachePosition endPos = mapToCache( 0, end );
00325 
00326     static const CachePosition NullPosition( -1, -1 );
00327     if( startPos == NullPosition )
00328     {
00329         rebuildCache();
00330         startPos = mapToCache( 0, start );
00331         endPos = mapToCache( 0, end );
00332         // The start position still isn't valid,
00333         // probably means that no resolution was set yet
00334         if( startPos == NullPosition ) {
00335             return;
00336         }
00337     }
00338 
00339     start = startPos.second;
00340     end = endPos.second;
00341 
00342     m_data.remove( start, end - start + 1 );
00343 }
00344 
00345 void CartesianDiagramDataCompressor::slotColumnsRemoved( const QModelIndex& parent, int start, int end )
00346 {
00347     if ( parent != m_rootIndex )
00348         return;
00349     Q_ASSERT( start <= end );
00350 
00351     const CachePosition startPos = mapToCache( 0, start );
00352     const CachePosition endPos = mapToCache( 0, end );
00353 
00354     start = startPos.second;
00355     end = endPos.second;
00356 
00357     static const CachePosition NullPosition( -1, -1 );
00358     if( startPos == NullPosition )
00359     {
00360         // Since we should already have rebuilt the cache, it won't help to rebuild it again.
00361         // Do not Q_ASSERT() though, since the resolution might simply not be set or we might now have 0 columns
00362         return;
00363     }
00364 
00365     for( int i = start; i < m_data.size(); ++i ) {
00366         for( int j = 0; j < m_data[i].size(); ++j ) {
00367             retrieveModelData( CachePosition( j, i ) );
00368         }
00369     }
00370 }
00371 
00372 void CartesianDiagramDataCompressor::slotModelHeaderDataChanged( Qt::Orientation orientation, int first, int last )
00373 {
00374     if( orientation != Qt::Vertical )
00375         return;
00376 
00377     const QModelIndex firstRow = m_model->index( 0, first, m_rootIndex );
00378     const QModelIndex lastRow = m_model->index( m_model->rowCount( m_rootIndex ) - 1, last, m_rootIndex );
00379 
00380     slotModelDataChanged( firstRow, lastRow );
00381 }
00382 
00383 void CartesianDiagramDataCompressor::slotModelDataChanged(
00384     const QModelIndex& topLeftIndex,
00385     const QModelIndex& bottomRightIndex )
00386 {
00387     if ( topLeftIndex.parent() != m_rootIndex )
00388         return;
00389     Q_ASSERT( topLeftIndex.parent() == bottomRightIndex.parent() );
00390     Q_ASSERT( topLeftIndex.row() <= bottomRightIndex.row() );
00391     Q_ASSERT( topLeftIndex.column() <= bottomRightIndex.column() );
00392     CachePosition topleft = mapToCache( topLeftIndex );
00393     CachePosition bottomright = mapToCache( bottomRightIndex );
00394     for ( int row = topleft.first; row <= bottomright.first; ++row )
00395         for ( int column = topleft.second; column <= bottomright.second; ++column )
00396             invalidate( CachePosition( row, column ) );
00397 }
00398 
00399 void CartesianDiagramDataCompressor::slotModelLayoutChanged()
00400 {
00401     rebuildCache();
00402     calculateSampleStepWidth();
00403 }
00404 
00405 void CartesianDiagramDataCompressor::slotDiagramLayoutChanged( AbstractDiagram* diagramBase )
00406 {
00407     AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* >( diagramBase );
00408     Q_ASSERT( diagram );
00409     if ( diagram->datasetDimension() != m_datasetDimension ) {
00410         setDatasetDimension( diagram->datasetDimension() );
00411     }
00412 }
00413 
00414 int CartesianDiagramDataCompressor::modelDataColumns() const
00415 {
00416     Q_ASSERT( m_datasetDimension != 0 );
00417     // only operational if there is a model and a resolution
00418     if ( m_model ) {
00419         const int columns = m_model->columnCount( m_rootIndex ) / m_datasetDimension;
00420 
00421         if( columns != m_data.size() )
00422         {
00423             rebuildCache();
00424         }
00425 
00426         Q_ASSERT( columns == m_data.size() );
00427         return columns;
00428     } else {
00429         return 0;
00430     }
00431 }
00432 
00433 int CartesianDiagramDataCompressor::modelDataRows() const
00434 {
00435     // only operational if there is a model, columns, and a resolution
00436     if ( m_model && m_model->columnCount( m_rootIndex ) > 0 && m_xResolution > 0 ) {
00437         return m_data.isEmpty() ? 0 : m_data.first().size();
00438     } else {
00439         return 0;
00440     }
00441 }
00442 
00443 void CartesianDiagramDataCompressor::setModel( QAbstractItemModel* model )
00444 {
00445     if ( m_model != 0 && m_model != model ) {
00446         disconnect( m_model, SIGNAL( headerDataChanged( Qt::Orientation, int, int ) ),
00447                  this, SLOT( slotModelHeaderDataChanged( Qt::Orientation, int, int ) ) );
00448         disconnect( m_model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ),
00449                  this, SLOT( slotModelDataChanged( QModelIndex, QModelIndex ) ) );
00450         disconnect( m_model, SIGNAL( layoutChanged() ),
00451                  this, SLOT( slotModelLayoutChanged() ) );
00452         disconnect( m_model, SIGNAL( rowsAboutToBeInserted( QModelIndex, int, int ) ),
00453                  this, SLOT( slotRowsAboutToBeInserted( QModelIndex, int, int ) ) );
00454         disconnect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ),
00455                  this, SLOT( slotRowsInserted( QModelIndex, int, int ) ) );
00456         disconnect( m_model, SIGNAL( rowsAboutToBeRemoved( QModelIndex, int, int ) ),
00457                  this, SLOT( slotRowsAboutToBeRemoved( QModelIndex, int, int ) ) );
00458         disconnect( m_model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ),
00459                  this, SLOT( slotRowsRemoved( QModelIndex, int, int ) ) );
00460         disconnect( m_model, SIGNAL( columnsAboutToBeInserted( QModelIndex, int, int ) ),
00461                  this, SLOT( slotColumnsAboutToBeInserted( QModelIndex, int, int ) ) );
00462         disconnect( m_model, SIGNAL( columnsInserted( QModelIndex, int, int ) ),
00463                  this, SLOT( slotColumnsInserted( QModelIndex, int, int ) ) );
00464         disconnect( m_model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ),
00465                  this, SLOT( slotColumnsRemoved( QModelIndex, int, int ) ) );
00466         disconnect( m_model, SIGNAL( columnsAboutToBeRemoved( QModelIndex, int, int ) ),
00467                  this, SLOT( slotColumnsAboutToBeRemoved( QModelIndex, int, int ) ) );
00468         disconnect( m_model, SIGNAL( modelReset() ),
00469                     this, SLOT( rebuildCache() ) );
00470         m_model = 0;
00471     }
00472 
00473     m_modelCache.setModel( model );
00474 
00475     if ( model != 0 ) {
00476         m_model = model;
00477         connect( m_model, SIGNAL( headerDataChanged( Qt::Orientation, int, int ) ),
00478                  SLOT( slotModelHeaderDataChanged( Qt::Orientation, int, int ) ) );
00479         connect( m_model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ),
00480                  SLOT( slotModelDataChanged( QModelIndex, QModelIndex ) ) );
00481         connect( m_model, SIGNAL( layoutChanged() ),
00482                  SLOT( slotModelLayoutChanged() ) );
00483         connect( m_model, SIGNAL( rowsAboutToBeInserted( QModelIndex, int, int ) ),
00484                  SLOT( slotRowsAboutToBeInserted( QModelIndex, int, int ) ) );
00485         connect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ),
00486                  SLOT( slotRowsInserted( QModelIndex, int, int ) ) );
00487         connect( m_model, SIGNAL( rowsAboutToBeRemoved( QModelIndex, int, int ) ),
00488                  SLOT( slotRowsAboutToBeRemoved( QModelIndex, int, int ) ) );
00489         connect( m_model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ),
00490                  SLOT( slotRowsRemoved( QModelIndex, int, int ) ) );
00491         connect( m_model, SIGNAL( columnsAboutToBeInserted( QModelIndex, int, int ) ),
00492                  SLOT( slotColumnsAboutToBeInserted( QModelIndex, int, int ) ) );
00493         connect( m_model, SIGNAL( columnsInserted( QModelIndex, int, int ) ),
00494                  SLOT( slotColumnsInserted( QModelIndex, int, int ) ) );
00495         connect( m_model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ),
00496                  SLOT( slotColumnsRemoved( QModelIndex, int, int ) ) );
00497         connect( m_model, SIGNAL( columnsAboutToBeRemoved( QModelIndex, int, int ) ),
00498                  SLOT( slotColumnsAboutToBeRemoved( QModelIndex, int, int ) ) );
00499         connect( m_model, SIGNAL( modelReset() ),
00500                     this, SLOT( rebuildCache() ) );
00501     }
00502     rebuildCache();
00503     calculateSampleStepWidth();
00504 }
00505 
00506 void CartesianDiagramDataCompressor::setRootIndex( const QModelIndex& root )
00507 {
00508     if ( m_rootIndex != root ) {
00509         Q_ASSERT( root.model() == m_model || !root.isValid() );
00510         m_rootIndex = root;
00511         m_modelCache.setRootIndex( root );
00512         rebuildCache();
00513         calculateSampleStepWidth();
00514     }
00515 }
00516 void CartesianDiagramDataCompressor::setResolution( int x, int y )
00517 {
00518     const int oldX = m_xResolution;
00519     const int oldY = m_yResolution;
00520 
00521     if( m_datasetDimension != 1 )
00522     {
00523         // just ignore the resolution in that case
00524         m_xResolution = m_model == 0 ? 0 : m_model->rowCount( m_rootIndex );
00525         m_yResolution = qMax( 0, y );
00526     }
00527     else if ( x != m_xResolution || y != m_yResolution ) {
00528         m_xResolution = qMax( 0, x );
00529         m_yResolution = qMax( 0, y );
00530         rebuildCache();
00531         calculateSampleStepWidth();
00532     }
00533 
00534     if( oldX != m_xResolution || ( oldY != m_yResolution && m_datasetDimension == 1 ) )
00535     {
00536         rebuildCache();
00537         calculateSampleStepWidth();
00538     }
00539 }
00540 
00541 void CartesianDiagramDataCompressor::clearCache()
00542 {
00543     for ( int column = 0; column < m_data.size(); ++column )
00544         m_data[column].fill( DataPoint() );
00545 }
00546 
00547 void CartesianDiagramDataCompressor::rebuildCache() const
00548 {
00549     Q_ASSERT( m_datasetDimension != 0 );
00550 
00551     m_data.clear();
00552     const int columnCount = m_model ? m_model->columnCount( m_rootIndex ) / m_datasetDimension : 0;
00553     const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution );
00554     m_data.resize( columnCount );
00555     for ( int i = 0; i < columnCount; ++i ) {
00556         m_data[i].resize( rowCount );
00557     }
00558     // also empty the attrs cache
00559     m_dataValueAttributesCache.clear();
00560 }
00561 
00562 const CartesianDiagramDataCompressor::DataPoint& CartesianDiagramDataCompressor::data( const CachePosition& position ) const
00563 {
00564     static DataPoint NullDataPoint;
00565     if ( ! isValidCachePosition( position ) ) return NullDataPoint;
00566     if ( ! isCached( position ) ) retrieveModelData( position );
00567     return m_data[ position.second ][ position.first ];
00568 }
00569         
00570 QPair< QPointF, QPointF > CartesianDiagramDataCompressor::dataBoundaries() const
00571 {
00572     const int colCount = modelDataColumns();
00573     double xMin = std::numeric_limits< double >::quiet_NaN();
00574     double xMax = std::numeric_limits< double >::quiet_NaN();
00575     double yMin = std::numeric_limits< double >::quiet_NaN();
00576     double yMax = std::numeric_limits< double >::quiet_NaN();
00577 
00578     for( int column = 0; column < colCount; ++column )
00579     {
00580         const DataPointVector& data = m_data[ column ];
00581         int row = 0;
00582         for( DataPointVector::const_iterator it = data.begin(); it != data.end(); ++it, ++row )
00583         {
00584             const DataPoint& p = *it;
00585             if( !p.index.isValid() )
00586                 retrieveModelData( CachePosition( row, column ) );
00587 
00588             const double valueX = ISNAN( p.key ) ? 0.0 : p.key;
00589             const double valueY = ISNAN( p.value ) ? 0.0 : p.value;
00590             if( ISNAN( xMin ) )
00591             {
00592                 xMin = valueX;
00593                 xMax = valueX;
00594                 yMin = valueY;
00595                 yMax = valueY;
00596             }
00597             else
00598             {
00599                 xMin = qMin( xMin, valueX );
00600                 xMax = qMax( xMax, valueX );
00601                 yMin = qMin( yMin, valueY );
00602                 yMax = qMax( yMax, valueY );
00603             }
00604         }
00605     }
00606 
00607     // NOTE: calculateDataBoundaries must return the *real* data boundaries!
00608     //       i.e. we may NOT fake yMin to be qMin( 0.0, yMin )
00609     //       (khz, 2008-01-24)
00610     const QPointF bottomLeft( QPointF( xMin, yMin ) );
00611     const QPointF topRight( QPointF( xMax, yMax ) );
00612     return QPair< QPointF, QPointF >( bottomLeft, topRight );
00613 }
00614         
00615 void CartesianDiagramDataCompressor::retrieveModelData( const CachePosition& position ) const
00616 {
00617     Q_ASSERT( isValidCachePosition( position ) );
00618     DataPoint result;
00619 
00620     switch(m_mode ) {
00621     case Precise:
00622     {
00623         bool forceHidden = false;
00624         result.hidden = true;
00625         const QModelIndexList indexes = mapToModel( position );
00626         if( m_datasetDimension != 1 )
00627         {
00628             Q_ASSERT( indexes.count() == 2 );
00629             
00630             // try the ColumnDataRole approach first
00631             const int xColumn = indexes.first().column();
00632             const int yColumn = indexes.last().column();
00633             const QVariantList xValues = m_model->headerData( xColumn, Qt::Horizontal, ColumnDataRole ).toList();
00634             const QVariantList yValues = m_model->headerData( yColumn, Qt::Horizontal, ColumnDataRole ).toList();
00635 
00636             if( !xValues.isEmpty() && !yValues.isEmpty() )
00637             {
00638                 Q_ASSERT( xValues.count() == yValues.count() );
00639                 int row = 0;
00640                 QVariantList::const_iterator itX = xValues.begin();
00641                 QVariantList::const_iterator itY = yValues.begin();
00642                 for( ; itX != xValues.end(); ++itX, ++itY, ++row )
00643                 {
00644                     DataPoint result;
00645                     result.index = m_model->index( row, xColumn, m_rootIndex );
00646                     if( !itX->isNull() )
00647                     {
00648                         result.key = itX->toDouble();
00649                         result.value = itY->toDouble();
00650                     }
00651                     m_data[ position.second ][ row ] = result;
00652                 }
00653                 return;
00654             }
00655 
00656             const QModelIndex& xIndex = indexes.first();
00657             const QModelIndex& yIndex = indexes.last();
00658             const double xData = m_modelCache.data( xIndex );
00659             const double yData = m_modelCache.data( yIndex );
00660             result.index = xIndex;
00661             result.key   = xData;
00662             result.value = yData;
00663         }
00664         else
00665         {
00666             if ( ! indexes.isEmpty() ) {
00667                 result.value = std::numeric_limits< double >::quiet_NaN();
00668                 result.key = 0.0;
00669                 Q_FOREACH( const QModelIndex& index, indexes ) {
00670                     const double value = m_modelCache.data( index );
00671                     if( !ISNAN( value ) )
00672                     {
00673                         result.value = ISNAN( result.value ) ? value : result.value + value;
00674                     }
00675                     result.key += index.row();
00676                 }
00677                 result.index = indexes.at( 0 );
00678                 result.key /= indexes.size();
00679                 result.value /= indexes.size();
00680             }
00681         }
00682         if( !forceHidden )
00683         {
00684         Q_FOREACH( const QModelIndex& index, indexes )
00685         {
00686             // the point is visible if any of the points at this pixel position is visible
00687             if ( qVariantValue<bool>( m_model->data( index, DataHiddenRole ) ) == false ) {
00688                 result.hidden = false;
00689             }
00690         }
00691         }
00692     }
00693     break;
00694     case SamplingSeven:
00695     default:
00696     {
00697     }
00698     break;
00699     };
00700 
00701     m_data[position.second][position.first] = result;
00702     Q_ASSERT( isCached( position ) );
00703 }
00704 
00705 CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache(
00706         const QModelIndex& index ) const
00707 {
00708     Q_ASSERT( m_datasetDimension != 0 );
00709 
00710     static const CachePosition NullPosition( -1, -1 );
00711     if ( ! index.isValid() ) return NullPosition;
00712     return mapToCache( index.row(), index.column() );
00713 }
00714 
00715 CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache(
00716         int row, int column ) const
00717 {
00718     Q_ASSERT( m_datasetDimension != 0 );
00719 
00720     if ( m_data.size() == 0 || m_data[0].size() == 0 ) return mapToCache( QModelIndex() );
00721     // assumption: indexes per column == 1
00722     if ( indexesPerPixel() == 0 ) return mapToCache( QModelIndex() );
00723     return CachePosition( static_cast< int >( ( row ) / indexesPerPixel() ), column / m_datasetDimension );
00724 }
00725 
00726 QModelIndexList CartesianDiagramDataCompressor::mapToModel( const CachePosition& position ) const
00727 {
00728     if ( isValidCachePosition( position ) ) {
00729         QModelIndexList indexes;
00730         if( m_datasetDimension == 2 )
00731         {
00732             indexes << m_model->index( position.first, position.second * 2, m_rootIndex );
00733             indexes << m_model->index( position.first, position.second * 2 + 1, m_rootIndex );
00734         }
00735         else
00736         {
00737         // assumption: indexes per column == 1
00738             const qreal ipp = indexesPerPixel();
00739             for ( int i = 0; i < ipp; ++i ) {
00740                 const QModelIndex index = m_model->index( qRound( position.first * ipp ) + i, position.second, m_rootIndex );
00741                 if( index.isValid() )
00742                     indexes << index;
00743             }
00744         }
00745         return indexes;
00746     } else {
00747         return QModelIndexList();
00748     }
00749 }
00750 
00751 qreal CartesianDiagramDataCompressor::indexesPerPixel() const
00752 {
00753     if ( m_data.size() == 0 ) return 0;
00754     if ( m_data[0].size() == 0 ) return 0;
00755     if ( ! m_model ) return 0;
00756     return static_cast< qreal >( m_model->rowCount( m_rootIndex ) ) / static_cast< qreal >( m_data[0].size() );
00757 }
00758 
00759 bool CartesianDiagramDataCompressor::isValidCachePosition( const CachePosition& position ) const
00760 {
00761     if ( ! m_model ) return false;
00762     if ( m_data.size() == 0 || m_data[0].size() == 0 ) return false;
00763     if ( position.second < 0 || position.second >= m_data.size() ) return false;
00764     if ( position.first < 0 || position.first >= m_data[0].size() ) return false;
00765     return true;
00766 }
00767 
00768 void CartesianDiagramDataCompressor::invalidate( const CachePosition& position )
00769 {
00770     if ( isValidCachePosition( position ) ) {
00771         m_data[position.second][position.first] = DataPoint();
00772         // Also invalidate the data value attributes at "position".
00773         // Otherwise the user overwrites the attributes without us noticing
00774         // it because we keep reading what's in the cache.
00775         m_dataValueAttributesCache.remove( position );
00776     }
00777 }
00778 
00779 bool CartesianDiagramDataCompressor::isCached( const CachePosition& position ) const
00780 {
00781     Q_ASSERT( isValidCachePosition( position ) );
00782     const DataPoint& p = m_data[position.second][position.first];
00783     return p.index.isValid();
00784 }
00785 
00786 void CartesianDiagramDataCompressor::calculateSampleStepWidth()
00787 {
00788     if ( m_mode == Precise ) {
00789         m_sampleStep = 1;
00790         return;
00791     }
00792 
00793     static unsigned int SomePrimes[] = {
00794         2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47,
00795         53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101,
00796         151, 211, 313, 401, 503, 607, 701, 811, 911, 1009,
00797         10037, 12911, 16001, 20011, 50021,
00798         100003, 137867, 199999, 500009, 707753, 1000003, 0
00799     }; // ... after that, having a model at all becomes impractical
00800 
00801     // we want at least 17 samples per data point, using a prime step width
00802     const double WantedSamples = 17;
00803     if ( WantedSamples > indexesPerPixel() ) {
00804         m_sampleStep = 1;
00805     } else {
00806         int i;
00807         for ( i = 0; SomePrimes[i] != 0; ++i ) {
00808             if ( WantedSamples * SomePrimes[i+1] > indexesPerPixel() ) {
00809                 break;
00810             }
00811         }
00812         m_sampleStep = SomePrimes[i];
00813         if ( SomePrimes[i] == 0 ) {
00814             m_sampleStep = SomePrimes[i-1];
00815         } else {
00816             m_sampleStep = SomePrimes[i];
00817         }
00818     }
00819 }
00820 
00821 void CartesianDiagramDataCompressor::setDatasetDimension( int dimension )
00822 {
00823     if ( dimension != m_datasetDimension ) {
00824         m_datasetDimension = dimension;
00825         rebuildCache();
00826         calculateSampleStepWidth();
00827     }
00828 }