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 }