# KDChartCartesianDiagramDataCompressor_p.cpp

Go to the documentation of this file.

    00001 /* -*- Mode: C++ -*-
    00002    KDChart - a multi-platform charting engine
    00003    */
    00004
    00005 /****************************************************************************
    00006  ** Copyright (C) 2005-2007 Klarälvdalens Datakonsult AB.  All rights reserved.
    00007  **
    00008  ** This file is part of the KD Chart library.
    00009  **
    00010  ** This file may be distributed and/or modified under the terms of the
    00011  ** GNU General Public License version 2 as published by the Free Software
    00012  ** Foundation and appearing in the file LICENSE.GPL included in the
    00013  ** packaging of this file.
    00014  **
    00015  ** Licensees holding valid commercial KD Chart licenses may use this file in
    00016  ** accordance with the KD Chart Commercial License Agreement provided with
    00017  ** the Software.
    00018  **
    00019  ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
    00020  ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
    00021  **
    00022  ** See http://www.kdab.net/kdchart for
    00023  **   information about KD Chart Commercial License Agreements.
    00024  **
    00025  ** Contact info@kdab.net if any conditions of this
    00026  ** licensing are not clear to you.
    00027  **
    00028  **********************************************************************/
    00029
    00030 #include <QtDebug>
    00031 #include <QAbstractItemModel>
    00032
    00033 #include "KDChartAbstractCartesianDiagram.h"
    00034 #include "KDChartCartesianDiagramDataCompressor_p.h"
    00035
    00036 using namespace KDChart;
    00037
    00038 CartesianDiagramDataCompressor::CartesianDiagramDataCompressor( QObject* parent )
    00039     : QObject( parent )
    00040     , m_mode( Precise )
    00041     , m_xResolution( 0 )
    00042     , m_yResolution( 0 )
    00043     , m_sampleStep( 0 )
    00044     , m_datasetDimension( 1 )
    00045 {
    00046     calculateSampleStepWidth();
    00047 }
    00048
    00049 void CartesianDiagramDataCompressor::slotRowsInserted( const QModelIndex& parent, int start, int end )
    00050 {
    00051     Q_UNUSED( parent );
    00052     Q_ASSERT( start <= end );
    00053
    00054     const CachePosition startPos = mapToCache( start, 0 );
    00055     const CachePosition endPos = mapToCache( end, 0 );
    00056
    00057     start = startPos.first;
    00058     end = endPos.first;
    00059
    00060     static const CachePosition NullPosition( -1, -1 );
    00061     if( startPos == NullPosition )
    00062         return rebuildCache();
    00063
    00064     for( int i = 0; i < m_data.size(); ++i )
    00065     {
    00066         Q_ASSERT( start >= 0 && start <= m_data[ i ].size() );
    00067         m_data[ i ].insert( start, end - start + 1, DataPoint() );
    00068     }
    00069 }
    00070
    00071 void CartesianDiagramDataCompressor::slotColumnsInserted( const QModelIndex& parent, int start, int end )
    00072 {
    00073     Q_UNUSED( parent );
    00074     Q_ASSERT( start <= end );
    00075
    00076
    00077     const CachePosition startPos = mapToCache( 0, start );
    00078     const CachePosition endPos = mapToCache( 0, end );
    00079
    00080     static const CachePosition NullPosition( -1, -1 );
    00081     if( startPos == NullPosition )
    00082         return rebuildCache();
    00083
    00084     start = startPos.second;
    00085     end = endPos.second;
    00086
    00087     const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution );
    00088     Q_ASSERT( start >= 0 && start <= m_data.size() );
    00089     m_data.insert( start, end - start + 1, QVector< DataPoint >( rowCount ) );
    00090 }
    00091
    00092 void CartesianDiagramDataCompressor::slotRowsRemoved( const QModelIndex& parent, int start, int end )
    00093 {
    00094     Q_UNUSED( parent );
    00095     Q_ASSERT( start <= end );
    00096
    00097     const CachePosition startPos = mapToCache( start, 0 );
    00098     const CachePosition endPos = mapToCache( end, 0 );
    00099
    00100     start = startPos.first;
    00101     end = endPos.first;
    00102
    00103     for( int i = 0; i < m_data.size(); ++i )
    00104     {
    00105         m_data[ i ].remove( start, end - start + 1 );
    00106     }
    00107 }
    00108
    00109 void CartesianDiagramDataCompressor::slotColumnsRemoved( const QModelIndex& parent, int start, int end )
    00110 {
    00111     Q_UNUSED( parent );
    00112     Q_ASSERT( start <= end );
    00113
    00114     const CachePosition startPos = mapToCache( 0, start );
    00115     const CachePosition endPos = mapToCache( 0, end );
    00116
    00117     start = startPos.second;
    00118     end = endPos.second;
    00119
    00120     m_data.remove( start, end - start + 1 );
    00121 }
    00122
    00123 void CartesianDiagramDataCompressor::slotModelHeaderDataChanged( Qt::Orientation orientation, int first, int last )
    00124 {
    00125     if( orientation != Qt::Vertical )
    00126         return;
    00127
    00128     const QModelIndex firstRow = m_model->index( 0, first, m_rootIndex );
    00129     const QModelIndex lastRow = m_model->index( m_model->rowCount( m_rootIndex ) - 1, last, m_rootIndex );
    00130
    00131     slotModelDataChanged( firstRow, lastRow );
    00132 }
    00133
    00134 void CartesianDiagramDataCompressor::slotModelDataChanged(
    00135     const QModelIndex& topLeftIndex,
    00136     const QModelIndex& bottomRightIndex )
    00137 {
    00138     Q_ASSERT( topLeftIndex.row() <= bottomRightIndex.row() );
    00139     Q_ASSERT( topLeftIndex.column() <= bottomRightIndex.column() );
    00140     CachePosition topleft = mapToCache( topLeftIndex );
    00141     CachePosition bottomright = mapToCache( bottomRightIndex );
    00142     for ( int row = topleft.first; row <= bottomright.first; ++row )
    00143         for ( int column = topleft.second; column <= bottomright.second; ++column )
    00144             invalidate( CachePosition( row, column ) );
    00145 }
    00146
    00147 void CartesianDiagramDataCompressor::slotModelLayoutChanged()
    00148 {
    00149     rebuildCache();
    00150     calculateSampleStepWidth();
    00151 }
    00152
    00153 void CartesianDiagramDataCompressor::slotDiagramLayoutChanged( AbstractDiagram* diagramBase )
    00154 {
    00155     AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* >( diagramBase );
    00156     Q_ASSERT( diagram );
    00157     if ( diagram->datasetDimension() != m_datasetDimension ) {
    00158         setDatasetDimension( diagram->datasetDimension() );
    00159     }
    00160 }
    00161
    00162 int CartesianDiagramDataCompressor::modelDataColumns() const
    00163 {
    00164     Q_ASSERT( m_datasetDimension != 0 );
    00165     // only operational if there is a model and a resolution
    00166     if ( m_model ) {
    00167         const int columns = m_model->columnCount( m_rootIndex ) / m_datasetDimension;
    00168
    00169         if( columns != m_data.size() )
    00170         {
    00171             rebuildCache();
    00172         }
    00173
    00174         Q_ASSERT( columns == m_data.size() );
    00175         return columns;
    00176     } else {
    00177         return 0;
    00178     }
    00179 }
    00180
    00181 int CartesianDiagramDataCompressor::modelDataRows() const
    00182 {
    00183     // only operational if there is a model, columns, and a resolution
    00184     if ( m_model && m_model->columnCount( m_rootIndex ) > 0 && m_xResolution > 0 ) {
    00185         return m_data[0].size();
    00186     } else {
    00187         return 0;
    00188     }
    00189 }
    00190
    00191 void CartesianDiagramDataCompressor::setModel( QAbstractItemModel* model )
    00192 {
    00193     if ( m_model != 0 && m_model != model ) {
    00194         disconnect( m_model, SIGNAL( headerDataChanged( Qt::Orientation, int, int ) ),
    00195                     this, SLOT( slotModelHeaderDataChanged( Qt::Orientation, int, int ) ) );
    00196         disconnect( m_model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ),
    00197                     this, SLOT( slotModelDataChanged( QModelIndex, QModelIndex ) ) );
    00198         disconnect( m_model, SIGNAL( layoutChanged() ),
    00199                     this, SLOT( slotModelLayoutChanged() ) );
    00200         disconnect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ),
    00201                     this, SLOT( slotRowsInserted( QModelIndex, int, int ) ) );
    00202         disconnect( m_model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ),
    00203                     this, SLOT( slotRowsRemoved( QModelIndex, int, int ) ) );
    00204         disconnect( m_model, SIGNAL( columnsInserted( QModelIndex, int, int ) ),
    00205                     this, SLOT( slotColumnsInserted( QModelIndex, int, int ) ) );
    00206         disconnect( m_model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ),
    00207                     this, SLOT( slotColumnsRemoved( QModelIndex, int, int ) ) );
    00208         m_model = 0;
    00209     }
    00210
    00211     if ( model != 0 ) {
    00212         m_model = model;
    00213         connect( m_model, SIGNAL( headerDataChanged( Qt::Orientation, int, int ) ),
    00214                  SLOT( slotModelHeaderDataChanged( Qt::Orientation, int, int ) ) );
    00215         connect( m_model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ),
    00216                  SLOT( slotModelDataChanged( QModelIndex, QModelIndex ) ) );
    00217         connect( m_model, SIGNAL( layoutChanged() ),
    00218                  SLOT( slotModelLayoutChanged() ) );
    00219         connect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ),
    00220                  SLOT( slotRowsInserted( QModelIndex, int, int ) ) );
    00221         connect( m_model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ),
    00222                  SLOT( slotRowsRemoved( QModelIndex, int, int ) ) );
    00223         connect( m_model, SIGNAL( columnsInserted( QModelIndex, int, int ) ),
    00224                  SLOT( slotColumnsInserted( QModelIndex, int, int ) ) );
    00225         connect( m_model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ),
    00226                  SLOT( slotColumnsRemoved( QModelIndex, int, int ) ) );
    00227     }
    00228
    00229     rebuildCache();
    00230     calculateSampleStepWidth();
    00231 }
    00232
    00233 void CartesianDiagramDataCompressor::setRootIndex( const QModelIndex& root )
    00234 {
    00235     if ( m_rootIndex != root ) {
    00236         m_rootIndex = root;
    00237         rebuildCache();
    00238         calculateSampleStepWidth();
    00239     }
    00240 }
    00241 void CartesianDiagramDataCompressor::setResolution( int x, int y )
    00242 {
    00243     const int oldX = m_xResolution;
    00244     const int oldY = m_yResolution;
    00245
    00246     if( m_datasetDimension != 1 )
    00247     {
    00248         // just ignore the resolution in that case
    00249         m_xResolution = m_model == 0 ? 0 : m_model->rowCount( m_rootIndex );
    00250         m_yResolution = qMax( 0, y );
    00251     }
    00252     else if ( x != m_xResolution || y != m_yResolution ) {
    00253         m_xResolution = qMax( 0, x );
    00254         m_yResolution = qMax( 0, y );
    00255         rebuildCache();
    00256         calculateSampleStepWidth();
    00257     }
    00258
    00259     if( oldX != m_xResolution || oldY != m_yResolution )
    00260     {
    00261         rebuildCache();
    00262         calculateSampleStepWidth();
    00263     }
    00264 }
    00265
    00266 void CartesianDiagramDataCompressor::clearCache()
    00267 {
    00268     for ( int column = 0; column < m_data.size(); ++column )
    00269         m_data[column].fill( DataPoint() );
    00270 }
    00271
    00272 void CartesianDiagramDataCompressor::rebuildCache() const
    00273 {
    00274     Q_ASSERT( m_datasetDimension != 0 );
    00275
    00276     m_data.clear();
    00277     const int columnCount = m_model ? m_model->columnCount( m_rootIndex ) / m_datasetDimension : 0;
    00278     const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution );
    00279     m_data.resize( columnCount );
    00280     for ( int i = 0; i < columnCount; ++i ) {
    00281         m_data[i].resize( rowCount );
    00282         m_data[i].fill( DataPoint() );
    00283     }
    00284 }
    00285
    00286 const CartesianDiagramDataCompressor::DataPoint& CartesianDiagramDataCompressor::data( const CachePosition& position ) const
    00287 {
    00288     static DataPoint NullDataPoint;
    00289     if ( ! isValidCachePosition( position ) ) return NullDataPoint;
    00290     if ( ! isCached( position ) ) retrieveModelData( position );
    00291     return m_data[ position.second ][ position.first ];
    00292 }
    00293
    00294 void CartesianDiagramDataCompressor::retrieveModelData( const CachePosition& position ) const
    00295 {
    00296     Q_ASSERT( isValidCachePosition( position ) );
    00297     DataPoint result;
    00298
    00299     switch(m_mode ) {
    00300     case Precise:
    00301     {
    00302         result.hidden = true;
    00303         const QModelIndexList indexes = mapToModel( position );
    00304         if( m_datasetDimension != 1 )
    00305         {
    00306             Q_ASSERT( indexes.count() == 2 );
    00307             const QModelIndex xIndex = indexes.first();
    00308             const QModelIndex yIndex = indexes.last();
    00309             result.index = xIndex;
    00310             result.key = m_model->data( xIndex ).toDouble();
    00311             result.value = m_model->data( yIndex ).toDouble();
    00312         }
    00313         else
    00314         {
    00315             if ( ! indexes.isEmpty() ) {
    00316                 Q_FOREACH( const QModelIndex& index, indexes ) {
    00317                     bool ok;
    00318                     const QVariant valueVariant = m_model->data( index, Qt::DisplayRole );
    00319                     const double value = valueVariant.toDouble( &ok );
    00320                     if( ok )
    00321                     {
    00322                         result.value += value;
    00323                         result.key += index.row();
    00324                     }
    00325                 }
    00326                 result.index = indexes.at( 0 );
    00327                 result.key /= indexes.size();
    00328                 result.value /= indexes.size();
    00329             }
    00330         }
    00331         Q_FOREACH( const QModelIndex& index, indexes )
    00332         {
    00333             // the point is visible if any of the points at this pixel position is visible
    00334             if ( qVariantValue<bool>( m_model->data( index, DataHiddenRole ) ) == false ) {
    00335                 result.hidden = false;
    00336             }
    00337         }
    00338     }
    00339     break;
    00340     case SamplingSeven:
    00341     default:
    00342     {
    00343     }
    00344     break;
    00345     };
    00346
    00347     m_data[position.second][position.first] = result;
    00348     Q_ASSERT( isCached( position ) );
    00349 }
    00350
    00351 CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache(
    00352     const QModelIndex& index ) const
    00353 {
    00354     Q_ASSERT( m_datasetDimension != 0 );
    00355
    00356     static const CachePosition NullPosition( -1, -1 );
    00357     if ( ! index.isValid() ) return NullPosition;
    00358     return mapToCache( index.row(), index.column() );
    00359 }
    00360
    00361 CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache(
    00362     int row, int column ) const
    00363 {
    00364     Q_ASSERT( m_datasetDimension != 0 );
    00365
    00366     if ( m_data.size() == 0 || m_data[0].size() == 0 ) return mapToCache( QModelIndex() );
    00367     // assumption: indexes per column == 1
    00368     if ( indexesPerPixel() == 0 ) return mapToCache( QModelIndex() );
    00369     return CachePosition( qRound( static_cast< qreal >( row ) / indexesPerPixel() ), column / m_datasetDimension );
    00370 }
    00371
    00372 QModelIndexList CartesianDiagramDataCompressor::mapToModel( const CachePosition& position ) const
    00373 {
    00374     if ( isValidCachePosition( position ) ) {
    00375         QModelIndexList indexes;
    00376         if( m_datasetDimension == 2 )
    00377         {
    00378             indexes << m_model->index( position.first, position.second * 2, m_rootIndex );
    00379             indexes << m_model->index( position.first, position.second * 2 + 1, m_rootIndex );
    00380         }
    00381         else
    00382         {
    00383         // assumption: indexes per column == 1
    00384             const qreal ipp = indexesPerPixel();
    00385             for ( int i = 0; i < ipp; ++i ) {
    00386                 indexes << m_model->index( qRound( position.first * ipp ), position.second, m_rootIndex );
    00387             }
    00388         }
    00389         return indexes;
    00390     } else {
    00391         return QModelIndexList();
    00392     }
    00393 }
    00394
    00395 qreal CartesianDiagramDataCompressor::indexesPerPixel() const
    00396 {
    00397     if ( m_data.size() == 0 ) return 0;
    00398     if ( m_data[0].size() == 0 ) return 0;
    00399     if ( ! m_model ) return 0;
    00400     return static_cast< qreal >( m_model->rowCount( m_rootIndex ) ) / static_cast< qreal >( m_data[0].size() );
    00401 }
    00402
    00403 bool CartesianDiagramDataCompressor::isValidCachePosition( const CachePosition& position ) const
    00404 {
    00405     if ( ! m_model ) return false;
    00406     if ( m_data.size() == 0 || m_data[0].size() == 0 ) return false;
    00407     if ( position.second < 0 || position.second >= m_data.size() ) return false;
    00408     if ( position.first < 0 || position.first >= m_data[0].size() ) return false;
    00409     return true;
    00410 }
    00411
    00412 void CartesianDiagramDataCompressor::invalidate( const CachePosition& position )
    00413 {
    00414     if ( isValidCachePosition( position ) )
    00415         m_data[position.second][position.first] = DataPoint();
    00416 }
    00417
    00418 bool CartesianDiagramDataCompressor::isCached( const CachePosition& position ) const
    00419 {
    00420     Q_ASSERT( isValidCachePosition( position ) );
    00421     return m_data[position.second][position.first].index.isValid();
    00422 }
    00423
    00424 void CartesianDiagramDataCompressor::calculateSampleStepWidth()
    00425 {
    00426     if ( m_mode == Precise ) {
    00427         m_sampleStep = 1;
    00428         return;
    00429     }
    00430
    00431     static unsigned int SomePrimes[] = {
    00432         2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47,
    00433         53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101,
    00434         151, 211, 313, 401, 503, 607, 701, 811, 911, 1009,
    00435         10037, 12911, 16001, 20011, 50021,
    00436         100003, 137867, 199999, 500009, 707753, 1000003, 0
    00437     }; // ... after that, having a model at all becomes impractical
    00438
    00439     // we want at least 17 samples per data point, using a prime step width
    00440     const double WantedSamples = 17;
    00441     if ( WantedSamples > indexesPerPixel() ) {
    00442         m_sampleStep = 1;
    00443     } else {
    00444         int i;
    00445         for ( i = 0; SomePrimes[i] != 0; ++i ) {
    00446             if ( WantedSamples * SomePrimes[i+1] > indexesPerPixel() ) {
    00447                 break;
    00448             }
    00449         }
    00450         m_sampleStep = SomePrimes[i];
    00451         if ( SomePrimes[i] == 0 ) {
    00452             m_sampleStep = SomePrimes[i-1];
    00453         } else {
    00454             m_sampleStep = SomePrimes[i];
    00455         }
    00456     }
    00457 }
    00458
    00459 void CartesianDiagramDataCompressor::setDatasetDimension( int dimension )
    00460 {
    00461     if ( dimension != m_datasetDimension ) {
    00462         m_datasetDimension = dimension;
    00463         rebuildCache();
    00464         calculateSampleStepWidth();
    00465     }
    00466 }