/**************************************************************************** ** Copyright (C) 2001-2011 Klaralvdalens Datakonsult AB. All rights reserved. ** ** This file is part of the KD Chart library. ** ** Licensees holding valid commercial KD Chart licenses may use this file in ** accordance with the KD Chart Commercial License Agreement provided with ** the Software. ** ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 and version 3 as published by the ** Free Software Foundation and appearing in the file LICENSE.GPL.txt included. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** Contact info@kdab.com if any conditions of this licensing are not ** clear to you. ** **********************************************************************/ #include "KDChartCartesianDiagramDataCompressor_p.h" #include #include #include "KDChartAbstractCartesianDiagram.h" #include using namespace KDChart; using namespace std; CartesianDiagramDataCompressor::CartesianDiagramDataCompressor( QObject* parent ) : QObject( parent ) , m_mode( Precise ) , m_xResolution( 0 ) , m_yResolution( 0 ) , m_sampleStep( 0 ) , m_datasetDimension( 1 ) { calculateSampleStepWidth(); } QModelIndexList CartesianDiagramDataCompressor::indexesAt( const CachePosition& position ) const { if ( isValidCachePosition( position ) ) { CachePosition posPrev( position ); if( m_datasetDimension == 2 ){ if(posPrev.second) --posPrev.second; }else{ if(posPrev.first) --posPrev.first; } const QModelIndexList indPrev = mapToModel( posPrev ); const QModelIndexList indCur = mapToModel( position ); QModelIndexList indexes; if( m_datasetDimension == 2 ) { const int iStart = (indPrev.empty() || indPrev==indCur) ? indCur.first().column() : indPrev.first().column() + 1; const int iEnd = indCur.last().column(); for( int i=iStart; i<=iEnd; ++i){ indexes << m_model->index( position.first, i, m_rootIndex ); } } else { const int iStart = (indPrev.empty() || indPrev==indCur) ? indCur.first().row() : indPrev.first().row() + 1; const int iEnd = (indCur.isEmpty()) ? iStart : indCur.first().row(); //qDebug()<index( i, position.second, m_rootIndex ); } } return indexes; } else { return QModelIndexList(); } } CartesianDiagramDataCompressor::DataValueAttributesList CartesianDiagramDataCompressor::aggregatedAttrs( AbstractDiagram * diagram, const QModelIndex & index, const CachePosition& position ) const { // return cached attrs, if any DataValueAttributesCache::const_iterator i = m_dataValueAttributesCache.constFind(position); if( i != m_dataValueAttributesCache.constEnd() ) return i.value(); // retrieve attrs from all cells between the prev. cell and the current one CartesianDiagramDataCompressor::DataValueAttributesList allAttrs; const QModelIndexList indexes( indexesAt( position ) ); KDAB_FOREACH( QModelIndex idx, indexes ) { DataValueAttributes attrs( diagram->dataValueAttributes( idx ) ); if( attrs.isVisible() ){ // make sure no duplicate attrs are stored bool isDuplicate = false; CartesianDiagramDataCompressor::DataValueAttributesList::const_iterator i = allAttrs.constBegin(); while (i != allAttrs.constEnd()) { if( i.value() == attrs ){ isDuplicate = true; break; } ++i; } if( !isDuplicate ){ //qDebug()<dataValueAttributes( index ); } // cache the attrs m_dataValueAttributesCache[position] = allAttrs; return allAttrs; } void CartesianDiagramDataCompressor::slotRowsAboutToBeInserted( const QModelIndex& parent, int start, int end ) { if ( parent != m_rootIndex ) return; Q_ASSERT( start <= end ); CachePosition startPos = mapToCache( start, 0 ); CachePosition endPos = mapToCache( end, 0 ); static const CachePosition NullPosition( -1, -1 ); if( startPos == NullPosition ) { rebuildCache(); startPos = mapToCache( start, 0 ); endPos = mapToCache( end, 0 ); // The start position still isn't valid, // means that no resolution was set yet or we're about to add the first rows if( startPos == NullPosition ) { return; } } start = startPos.first; end = endPos.first; for( int i = 0; i < m_data.size(); ++i ) { Q_ASSERT( start >= 0 && start <= m_data[ i ].size() ); m_data[ i ].insert( start, end - start + 1, DataPoint() ); } } void CartesianDiagramDataCompressor::slotRowsInserted( const QModelIndex& parent, int start, int end ) { if ( parent != m_rootIndex ) return; Q_ASSERT( start <= end ); CachePosition startPos = mapToCache( start, 0 ); CachePosition endPos = mapToCache( end, 0 ); static const CachePosition NullPosition( -1, -1 ); if( startPos == NullPosition ) { // Rebuild the cache at this point if we have added the first rows rebuildCache(); startPos = mapToCache( start, 0 ); endPos = mapToCache( end, 0 ); // The start position still isn't valid, // means that no resolution was set yet if( startPos == NullPosition ) { return; } } start = startPos.first; end = endPos.first; for( int i = 0; i < m_data.size(); ++i ) { for( int j = start; j < m_data[i].size(); ++j ) { retrieveModelData( CachePosition( j, i ) ); } } } void CartesianDiagramDataCompressor::slotColumnsAboutToBeInserted( const QModelIndex& parent, int start, int end ) { if ( parent != m_rootIndex ) return; Q_ASSERT( start <= end ); CachePosition startPos = mapToCache( 0, start ); CachePosition endPos = mapToCache( 0, end ); static const CachePosition NullPosition( -1, -1 ); if( startPos == NullPosition ) { rebuildCache(); startPos = mapToCache( 0, start ); endPos = mapToCache( 0, end ); // The start position still isn't valid, // means that no resolution was set yet or we're about to add the first columns if( startPos == NullPosition ) { return; } } start = startPos.second; end = endPos.second; const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution ); Q_ASSERT( start >= 0 && start <= m_data.size() ); m_data.insert( start, end - start + 1, QVector< DataPoint >( rowCount ) ); } void CartesianDiagramDataCompressor::slotColumnsInserted( const QModelIndex& parent, int start, int end ) { if ( parent != m_rootIndex ) return; Q_ASSERT( start <= end ); CachePosition startPos = mapToCache( 0, start ); CachePosition endPos = mapToCache( 0, end ); static const CachePosition NullPosition( -1, -1 ); if( startPos == NullPosition ) { // Rebuild the cache at this point if we have added the first columns rebuildCache(); startPos = mapToCache( 0, start ); endPos = mapToCache( 0, end ); // The start position still isn't valid, // means that no resolution was set yet if( startPos == NullPosition ) { return; } } start = startPos.second; end = endPos.second; for( int i = start; i < m_data.size(); ++i ) { for(int j = 0; j < m_data[i].size(); ++j ) { retrieveModelData( CachePosition( j, i ) ); } } } void CartesianDiagramDataCompressor::slotRowsAboutToBeRemoved( const QModelIndex& parent, int start, int end ) { if ( parent != m_rootIndex ) return; Q_ASSERT( start <= end ); CachePosition startPos = mapToCache( start, 0 ); CachePosition endPos = mapToCache( end, 0 ); static const CachePosition NullPosition( -1, -1 ); if( startPos == NullPosition ) { rebuildCache(); startPos = mapToCache( start, 0 ); endPos = mapToCache( end, 0 ); // The start position still isn't valid, // probably means that no resolution was set yet if( startPos == NullPosition ) { return; } } start = startPos.first; end = endPos.first; for( int i = 0; i < m_data.size(); ++i ) { m_data[ i ].remove( start, end - start + 1 ); } } void CartesianDiagramDataCompressor::slotRowsRemoved( const QModelIndex& parent, int start, int end ) { if ( parent != m_rootIndex ) return; Q_ASSERT( start <= end ); CachePosition startPos = mapToCache( start, 0 ); CachePosition endPos = mapToCache( end, 0 ); start = startPos.first; end = endPos.first; static const CachePosition NullPosition( -1, -1 ); if( startPos == NullPosition ) { // Since we should already have rebuilt the cache, it won't help to rebuild it again. // Do not Q_ASSERT() though, since the resolution might simply not be set or we might now have 0 rows return; } for( int i = 0; i < m_data.size(); ++i ) { for(int j = start; j < m_data[i].size(); ++j ) { retrieveModelData( CachePosition( j, i ) ); } } } void CartesianDiagramDataCompressor::slotColumnsAboutToBeRemoved( const QModelIndex& parent, int start, int end ) { if ( parent != m_rootIndex ) return; Q_ASSERT( start <= end ); CachePosition startPos = mapToCache( 0, start ); CachePosition endPos = mapToCache( 0, end ); static const CachePosition NullPosition( -1, -1 ); if( startPos == NullPosition ) { rebuildCache(); startPos = mapToCache( 0, start ); endPos = mapToCache( 0, end ); // The start position still isn't valid, // probably means that no resolution was set yet if( startPos == NullPosition ) { return; } } start = startPos.second; end = endPos.second; m_data.remove( start, end - start + 1 ); } void CartesianDiagramDataCompressor::slotColumnsRemoved( const QModelIndex& parent, int start, int end ) { if ( parent != m_rootIndex ) return; Q_ASSERT( start <= end ); const CachePosition startPos = mapToCache( 0, start ); const CachePosition endPos = mapToCache( 0, end ); start = startPos.second; end = endPos.second; static const CachePosition NullPosition( -1, -1 ); if( startPos == NullPosition ) { // Since we should already have rebuilt the cache, it won't help to rebuild it again. // Do not Q_ASSERT() though, since the resolution might simply not be set or we might now have 0 columns return; } for( int i = start; i < m_data.size(); ++i ) { for( int j = 0; j < m_data[i].size(); ++j ) { retrieveModelData( CachePosition( j, i ) ); } } } void CartesianDiagramDataCompressor::slotModelHeaderDataChanged( Qt::Orientation orientation, int first, int last ) { if( orientation != Qt::Vertical ) return; const QModelIndex firstRow = m_model->index( 0, first, m_rootIndex ); const QModelIndex lastRow = m_model->index( m_model->rowCount( m_rootIndex ) - 1, last, m_rootIndex ); slotModelDataChanged( firstRow, lastRow ); } void CartesianDiagramDataCompressor::slotModelDataChanged( const QModelIndex& topLeftIndex, const QModelIndex& bottomRightIndex ) { if ( topLeftIndex.parent() != m_rootIndex ) return; Q_ASSERT( topLeftIndex.parent() == bottomRightIndex.parent() ); Q_ASSERT( topLeftIndex.row() <= bottomRightIndex.row() ); Q_ASSERT( topLeftIndex.column() <= bottomRightIndex.column() ); CachePosition topleft = mapToCache( topLeftIndex ); CachePosition bottomright = mapToCache( bottomRightIndex ); for ( int row = topleft.first; row <= bottomright.first; ++row ) for ( int column = topleft.second; column <= bottomright.second; ++column ) invalidate( CachePosition( row, column ) ); } void CartesianDiagramDataCompressor::slotModelLayoutChanged() { rebuildCache(); calculateSampleStepWidth(); } void CartesianDiagramDataCompressor::slotDiagramLayoutChanged( AbstractDiagram* diagramBase ) { AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* >( diagramBase ); Q_ASSERT( diagram ); if ( diagram->datasetDimension() != m_datasetDimension ) { setDatasetDimension( diagram->datasetDimension() ); } } int CartesianDiagramDataCompressor::modelDataColumns() const { Q_ASSERT( m_datasetDimension != 0 ); // only operational if there is a model and a resolution if ( m_model ) { const int columns = m_model->columnCount( m_rootIndex ) / m_datasetDimension; if( columns != m_data.size() ) { rebuildCache(); } Q_ASSERT( columns == m_data.size() ); return columns; } else { return 0; } } int CartesianDiagramDataCompressor::modelDataRows() const { // only operational if there is a model, columns, and a resolution if ( m_model && m_model->columnCount( m_rootIndex ) > 0 && m_xResolution > 0 ) { return m_data.isEmpty() ? 0 : m_data.first().size(); } else { return 0; } } void CartesianDiagramDataCompressor::setModel( QAbstractItemModel* model ) { if ( m_model != 0 && m_model != model ) { disconnect( m_model, SIGNAL( headerDataChanged( Qt::Orientation, int, int ) ), this, SLOT( slotModelHeaderDataChanged( Qt::Orientation, int, int ) ) ); disconnect( m_model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ), this, SLOT( slotModelDataChanged( QModelIndex, QModelIndex ) ) ); disconnect( m_model, SIGNAL( layoutChanged() ), this, SLOT( slotModelLayoutChanged() ) ); disconnect( m_model, SIGNAL( rowsAboutToBeInserted( QModelIndex, int, int ) ), this, SLOT( slotRowsAboutToBeInserted( QModelIndex, int, int ) ) ); disconnect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ), this, SLOT( slotRowsInserted( QModelIndex, int, int ) ) ); disconnect( m_model, SIGNAL( rowsAboutToBeRemoved( QModelIndex, int, int ) ), this, SLOT( slotRowsAboutToBeRemoved( QModelIndex, int, int ) ) ); disconnect( m_model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ), this, SLOT( slotRowsRemoved( QModelIndex, int, int ) ) ); disconnect( m_model, SIGNAL( columnsAboutToBeInserted( QModelIndex, int, int ) ), this, SLOT( slotColumnsAboutToBeInserted( QModelIndex, int, int ) ) ); disconnect( m_model, SIGNAL( columnsInserted( QModelIndex, int, int ) ), this, SLOT( slotColumnsInserted( QModelIndex, int, int ) ) ); disconnect( m_model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ), this, SLOT( slotColumnsRemoved( QModelIndex, int, int ) ) ); disconnect( m_model, SIGNAL( columnsAboutToBeRemoved( QModelIndex, int, int ) ), this, SLOT( slotColumnsAboutToBeRemoved( QModelIndex, int, int ) ) ); disconnect( m_model, SIGNAL( modelReset() ), this, SLOT( rebuildCache() ) ); m_model = 0; } m_modelCache.setModel( model ); if ( model != 0 ) { m_model = model; connect( m_model, SIGNAL( headerDataChanged( Qt::Orientation, int, int ) ), SLOT( slotModelHeaderDataChanged( Qt::Orientation, int, int ) ) ); connect( m_model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ), SLOT( slotModelDataChanged( QModelIndex, QModelIndex ) ) ); connect( m_model, SIGNAL( layoutChanged() ), SLOT( slotModelLayoutChanged() ) ); connect( m_model, SIGNAL( rowsAboutToBeInserted( QModelIndex, int, int ) ), SLOT( slotRowsAboutToBeInserted( QModelIndex, int, int ) ) ); connect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ), SLOT( slotRowsInserted( QModelIndex, int, int ) ) ); connect( m_model, SIGNAL( rowsAboutToBeRemoved( QModelIndex, int, int ) ), SLOT( slotRowsAboutToBeRemoved( QModelIndex, int, int ) ) ); connect( m_model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ), SLOT( slotRowsRemoved( QModelIndex, int, int ) ) ); connect( m_model, SIGNAL( columnsAboutToBeInserted( QModelIndex, int, int ) ), SLOT( slotColumnsAboutToBeInserted( QModelIndex, int, int ) ) ); connect( m_model, SIGNAL( columnsInserted( QModelIndex, int, int ) ), SLOT( slotColumnsInserted( QModelIndex, int, int ) ) ); connect( m_model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ), SLOT( slotColumnsRemoved( QModelIndex, int, int ) ) ); connect( m_model, SIGNAL( columnsAboutToBeRemoved( QModelIndex, int, int ) ), SLOT( slotColumnsAboutToBeRemoved( QModelIndex, int, int ) ) ); connect( m_model, SIGNAL( modelReset() ), this, SLOT( rebuildCache() ) ); } rebuildCache(); calculateSampleStepWidth(); } void CartesianDiagramDataCompressor::setRootIndex( const QModelIndex& root ) { if ( m_rootIndex != root ) { Q_ASSERT( root.model() == m_model || !root.isValid() ); m_rootIndex = root; m_modelCache.setRootIndex( root ); rebuildCache(); calculateSampleStepWidth(); } } void CartesianDiagramDataCompressor::setResolution( int x, int y ) { const int oldX = m_xResolution; const int oldY = m_yResolution; if( m_datasetDimension != 1 ) { // just ignore the resolution in that case m_xResolution = m_model == 0 ? 0 : m_model->rowCount( m_rootIndex ); m_yResolution = qMax( 0, y ); } else if ( x != m_xResolution || y != m_yResolution ) { m_xResolution = qMax( 0, x ); m_yResolution = qMax( 0, y ); rebuildCache(); calculateSampleStepWidth(); } if( oldX != m_xResolution || ( oldY != m_yResolution && m_datasetDimension == 1 ) ) { rebuildCache(); calculateSampleStepWidth(); } } void CartesianDiagramDataCompressor::clearCache() { for ( int column = 0; column < m_data.size(); ++column ) m_data[column].fill( DataPoint() ); } void CartesianDiagramDataCompressor::rebuildCache() const { Q_ASSERT( m_datasetDimension != 0 ); m_data.clear(); const int columnCount = m_model ? m_model->columnCount( m_rootIndex ) / m_datasetDimension : 0; const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution ); m_data.resize( columnCount ); for ( int i = 0; i < columnCount; ++i ) { m_data[i].resize( rowCount ); } // also empty the attrs cache m_dataValueAttributesCache.clear(); } const CartesianDiagramDataCompressor::DataPoint& CartesianDiagramDataCompressor::data( const CachePosition& position ) const { static DataPoint NullDataPoint; if ( ! isValidCachePosition( position ) ) return NullDataPoint; if ( ! isCached( position ) ) retrieveModelData( position ); return m_data[ position.second ][ position.first ]; } QPair< QPointF, QPointF > CartesianDiagramDataCompressor::dataBoundaries() const { const int colCount = modelDataColumns(); double xMin = std::numeric_limits< double >::quiet_NaN(); double xMax = std::numeric_limits< double >::quiet_NaN(); double yMin = std::numeric_limits< double >::quiet_NaN(); double yMax = std::numeric_limits< double >::quiet_NaN(); for( int column = 0; column < colCount; ++column ) { const DataPointVector& data = m_data[ column ]; int row = 0; for( DataPointVector::const_iterator it = data.begin(); it != data.end(); ++it, ++row ) { const DataPoint& p = *it; if( !p.index.isValid() ) retrieveModelData( CachePosition( row, column ) ); const double valueX = ISNAN( p.key ) ? 0.0 : p.key; const double valueY = ISNAN( p.value ) ? 0.0 : p.value; if( ISNAN( xMin ) ) { xMin = valueX; xMax = valueX; yMin = valueY; yMax = valueY; } else { xMin = qMin( xMin, valueX ); xMax = qMax( xMax, valueX ); yMin = qMin( yMin, valueY ); yMax = qMax( yMax, valueY ); } } } // NOTE: calculateDataBoundaries must return the *real* data boundaries! // i.e. we may NOT fake yMin to be qMin( 0.0, yMin ) // (khz, 2008-01-24) const QPointF bottomLeft( QPointF( xMin, yMin ) ); const QPointF topRight( QPointF( xMax, yMax ) ); return QPair< QPointF, QPointF >( bottomLeft, topRight ); } void CartesianDiagramDataCompressor::retrieveModelData( const CachePosition& position ) const { Q_ASSERT( isValidCachePosition( position ) ); DataPoint result; switch(m_mode ) { case Precise: { bool forceHidden = false; result.hidden = true; const QModelIndexList indexes = mapToModel( position ); if( m_datasetDimension != 1 ) { Q_ASSERT( indexes.count() == 2 ); // try the ColumnDataRole approach first const int xColumn = indexes.first().column(); const int yColumn = indexes.last().column(); const QVariantList xValues = m_model->headerData( xColumn, Qt::Horizontal, ColumnDataRole ).toList(); const QVariantList yValues = m_model->headerData( yColumn, Qt::Horizontal, ColumnDataRole ).toList(); if( !xValues.isEmpty() && !yValues.isEmpty() ) { Q_ASSERT( xValues.count() == yValues.count() ); int row = 0; QVariantList::const_iterator itX = xValues.begin(); QVariantList::const_iterator itY = yValues.begin(); for( ; itX != xValues.end(); ++itX, ++itY, ++row ) { DataPoint result; result.index = m_model->index( row, xColumn, m_rootIndex ); if( !itX->isNull() ) { result.key = itX->toDouble(); result.value = itY->toDouble(); } m_data[ position.second ][ row ] = result; } return; } const QModelIndex& xIndex = indexes.first(); const QModelIndex& yIndex = indexes.last(); const double xData = m_modelCache.data( xIndex ); const double yData = m_modelCache.data( yIndex ); result.index = xIndex; result.key = xData; result.value = yData; } else { if ( ! indexes.isEmpty() ) { result.value = std::numeric_limits< double >::quiet_NaN(); result.key = 0.0; Q_FOREACH( const QModelIndex& index, indexes ) { const double value = m_modelCache.data( index ); if( !ISNAN( value ) ) { result.value = ISNAN( result.value ) ? value : result.value + value; } result.key += index.row(); } result.index = indexes.at( 0 ); result.key /= indexes.size(); result.value /= indexes.size(); } } if( !forceHidden ) { Q_FOREACH( const QModelIndex& index, indexes ) { // the point is visible if any of the points at this pixel position is visible if ( qVariantValue( m_model->data( index, DataHiddenRole ) ) == false ) { result.hidden = false; } } } } break; case SamplingSeven: default: { } break; }; m_data[position.second][position.first] = result; Q_ASSERT( isCached( position ) ); } CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache( const QModelIndex& index ) const { Q_ASSERT( m_datasetDimension != 0 ); static const CachePosition NullPosition( -1, -1 ); if ( ! index.isValid() ) return NullPosition; return mapToCache( index.row(), index.column() ); } CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache( int row, int column ) const { Q_ASSERT( m_datasetDimension != 0 ); if ( m_data.size() == 0 || m_data[0].size() == 0 ) return mapToCache( QModelIndex() ); // assumption: indexes per column == 1 if ( indexesPerPixel() == 0 ) return mapToCache( QModelIndex() ); return CachePosition( static_cast< int >( ( row ) / indexesPerPixel() ), column / m_datasetDimension ); } QModelIndexList CartesianDiagramDataCompressor::mapToModel( const CachePosition& position ) const { if ( isValidCachePosition( position ) ) { QModelIndexList indexes; if( m_datasetDimension == 2 ) { indexes << m_model->index( position.first, position.second * 2, m_rootIndex ); indexes << m_model->index( position.first, position.second * 2 + 1, m_rootIndex ); } else { // assumption: indexes per column == 1 const qreal ipp = indexesPerPixel(); for ( int i = 0; i < ipp; ++i ) { const QModelIndex index = m_model->index( qRound( position.first * ipp ) + i, position.second, m_rootIndex ); if( index.isValid() ) indexes << index; } } return indexes; } else { return QModelIndexList(); } } qreal CartesianDiagramDataCompressor::indexesPerPixel() const { if ( m_data.size() == 0 ) return 0; if ( m_data[0].size() == 0 ) return 0; if ( ! m_model ) return 0; return static_cast< qreal >( m_model->rowCount( m_rootIndex ) ) / static_cast< qreal >( m_data[0].size() ); } bool CartesianDiagramDataCompressor::isValidCachePosition( const CachePosition& position ) const { if ( ! m_model ) return false; if ( m_data.size() == 0 || m_data[0].size() == 0 ) return false; if ( position.second < 0 || position.second >= m_data.size() ) return false; if ( position.first < 0 || position.first >= m_data[position.second].size() ) return false; return true; } void CartesianDiagramDataCompressor::invalidate( const CachePosition& position ) { if ( isValidCachePosition( position ) ) { m_data[position.second][position.first] = DataPoint(); // Also invalidate the data value attributes at "position". // Otherwise the user overwrites the attributes without us noticing // it because we keep reading what's in the cache. m_dataValueAttributesCache.remove( position ); } } bool CartesianDiagramDataCompressor::isCached( const CachePosition& position ) const { Q_ASSERT( isValidCachePosition( position ) ); const DataPoint& p = m_data[position.second][position.first]; return p.index.isValid(); } void CartesianDiagramDataCompressor::calculateSampleStepWidth() { if ( m_mode == Precise ) { m_sampleStep = 1; return; } static unsigned int SomePrimes[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 151, 211, 313, 401, 503, 607, 701, 811, 911, 1009, 10037, 12911, 16001, 20011, 50021, 100003, 137867, 199999, 500009, 707753, 1000003, 0 }; // ... after that, having a model at all becomes impractical // we want at least 17 samples per data point, using a prime step width const double WantedSamples = 17; if ( WantedSamples > indexesPerPixel() ) { m_sampleStep = 1; } else { int i; for ( i = 0; SomePrimes[i] != 0; ++i ) { if ( WantedSamples * SomePrimes[i+1] > indexesPerPixel() ) { break; } } m_sampleStep = SomePrimes[i]; if ( SomePrimes[i] == 0 ) { m_sampleStep = SomePrimes[i-1]; } else { m_sampleStep = SomePrimes[i]; } } } void CartesianDiagramDataCompressor::setDatasetDimension( int dimension ) { if ( dimension != m_datasetDimension ) { m_datasetDimension = dimension; rebuildCache(); calculateSampleStepWidth(); } }