KD Chart 2 [rev.2.4]
|
00001 /**************************************************************************** 00002 ** Copyright (C) 2001-2012 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.txt 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 columnDivisor = m_datasetDimension != 2 ? 1 : m_datasetDimension; 00553 const int columnCount = m_model ? m_model->columnCount( m_rootIndex ) / columnDivisor : 0; 00554 const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution ); 00555 m_data.resize( columnCount ); 00556 for ( int i = 0; i < columnCount; ++i ) { 00557 m_data[i].resize( rowCount ); 00558 } 00559 // also empty the attrs cache 00560 m_dataValueAttributesCache.clear(); 00561 } 00562 00563 const CartesianDiagramDataCompressor::DataPoint& CartesianDiagramDataCompressor::data( const CachePosition& position ) const 00564 { 00565 static DataPoint NullDataPoint; 00566 if ( ! isValidCachePosition( position ) ) return NullDataPoint; 00567 if ( ! isCached( position ) ) retrieveModelData( position ); 00568 return m_data[ position.second ][ position.first ]; 00569 } 00570 00571 QPair< QPointF, QPointF > CartesianDiagramDataCompressor::dataBoundaries() const 00572 { 00573 const int colCount = modelDataColumns(); 00574 double xMin = std::numeric_limits< double >::quiet_NaN(); 00575 double xMax = std::numeric_limits< double >::quiet_NaN(); 00576 double yMin = std::numeric_limits< double >::quiet_NaN(); 00577 double yMax = std::numeric_limits< double >::quiet_NaN(); 00578 00579 for( int column = 0; column < colCount; ++column ) 00580 { 00581 const DataPointVector& data = m_data[ column ]; 00582 int row = 0; 00583 for( DataPointVector::const_iterator it = data.begin(); it != data.end(); ++it, ++row ) 00584 { 00585 const DataPoint& p = *it; 00586 if( !p.index.isValid() ) 00587 retrieveModelData( CachePosition( row, column ) ); 00588 00589 const double valueX = ISNAN( p.key ) ? 0.0 : p.key; 00590 const double valueY = ISNAN( p.value ) ? 0.0 : p.value; 00591 if( ISNAN( xMin ) ) 00592 { 00593 xMin = valueX; 00594 xMax = valueX; 00595 yMin = valueY; 00596 yMax = valueY; 00597 } 00598 else 00599 { 00600 xMin = qMin( xMin, valueX ); 00601 xMax = qMax( xMax, valueX ); 00602 yMin = qMin( yMin, valueY ); 00603 yMax = qMax( yMax, valueY ); 00604 } 00605 } 00606 } 00607 00608 // NOTE: calculateDataBoundaries must return the *real* data boundaries! 00609 // i.e. we may NOT fake yMin to be qMin( 0.0, yMin ) 00610 // (khz, 2008-01-24) 00611 const QPointF bottomLeft( xMin, yMin ); 00612 const QPointF topRight( xMax, yMax ); 00613 return QPair< QPointF, QPointF >( bottomLeft, topRight ); 00614 } 00615 00616 void CartesianDiagramDataCompressor::retrieveModelData( const CachePosition& position ) const 00617 { 00618 Q_ASSERT( isValidCachePosition( position ) ); 00619 DataPoint result; 00620 00621 switch(m_mode ) { 00622 case Precise: 00623 { 00624 bool forceHidden = false; 00625 result.hidden = true; 00626 const QModelIndexList indexes = mapToModel( position ); 00627 if( m_datasetDimension == 2 ) 00628 { 00629 Q_ASSERT( indexes.count() == 2 ); 00630 00631 // try the ColumnDataRole approach first 00632 const int xColumn = indexes.first().column(); 00633 const int yColumn = indexes.last().column(); 00634 const QVariantList xValues = m_model->headerData( xColumn, Qt::Horizontal, ColumnDataRole ).toList(); 00635 const QVariantList yValues = m_model->headerData( yColumn, Qt::Horizontal, ColumnDataRole ).toList(); 00636 00637 if( !xValues.isEmpty() && !yValues.isEmpty() ) 00638 { 00639 Q_ASSERT( xValues.count() == yValues.count() ); 00640 int row = 0; 00641 QVariantList::const_iterator itX = xValues.begin(); 00642 QVariantList::const_iterator itY = yValues.begin(); 00643 for( ; itX != xValues.end(); ++itX, ++itY, ++row ) 00644 { 00645 DataPoint result; 00646 result.index = m_model->index( row, xColumn, m_rootIndex ); 00647 if( !itX->isNull() ) 00648 { 00649 result.key = itX->toDouble(); 00650 result.value = itY->toDouble(); 00651 } 00652 m_data[ position.second ][ row ] = result; 00653 } 00654 return; 00655 } 00656 00657 const QModelIndex& xIndex = indexes.first(); 00658 const QModelIndex& yIndex = indexes.last(); 00659 const double xData = m_modelCache.data( xIndex ); 00660 const double yData = m_modelCache.data( yIndex ); 00661 result.index = xIndex; 00662 result.key = xData; 00663 result.value = yData; 00664 } 00665 else 00666 { 00667 if ( ! indexes.isEmpty() ) { 00668 result.value = std::numeric_limits< double >::quiet_NaN(); 00669 result.key = 0.0; 00670 Q_FOREACH( const QModelIndex& index, indexes ) { 00671 const double value = m_modelCache.data( index ); 00672 if( !ISNAN( value ) ) 00673 { 00674 result.value = ISNAN( result.value ) ? value : result.value + value; 00675 } 00676 result.key += index.row(); 00677 } 00678 result.index = indexes.at( 0 ); 00679 result.key /= indexes.size(); 00680 result.value /= indexes.size(); 00681 } 00682 } 00683 if( !forceHidden ) 00684 { 00685 Q_FOREACH( const QModelIndex& index, indexes ) 00686 { 00687 // the point is visible if any of the points at this pixel position is visible 00688 if ( qVariantValue<bool>( m_model->data( index, DataHiddenRole ) ) == false ) { 00689 result.hidden = false; 00690 } 00691 } 00692 } 00693 } 00694 break; 00695 case SamplingSeven: 00696 default: 00697 { 00698 } 00699 break; 00700 }; 00701 00702 m_data[position.second][position.first] = result; 00703 Q_ASSERT( isCached( position ) ); 00704 } 00705 00706 CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache( 00707 const QModelIndex& index ) const 00708 { 00709 Q_ASSERT( m_datasetDimension != 0 ); 00710 00711 static const CachePosition NullPosition( -1, -1 ); 00712 if ( ! index.isValid() ) return NullPosition; 00713 return mapToCache( index.row(), index.column() ); 00714 } 00715 00716 CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache( 00717 int row, int column ) const 00718 { 00719 Q_ASSERT( m_datasetDimension != 0 ); 00720 00721 if ( m_data.size() == 0 || m_data[0].size() == 0 ) return mapToCache( QModelIndex() ); 00722 // assumption: indexes per column == 1 00723 if ( indexesPerPixel() == 0 ) return mapToCache( QModelIndex() ); 00724 return CachePosition( static_cast< int >( ( row ) / indexesPerPixel() ), column / m_datasetDimension ); 00725 } 00726 00727 QModelIndexList CartesianDiagramDataCompressor::mapToModel( const CachePosition& position ) const 00728 { 00729 if ( isValidCachePosition( position ) ) { 00730 QModelIndexList indexes; 00731 if( m_datasetDimension == 2 ) 00732 { 00733 indexes << m_model->index( position.first, position.second * 2, m_rootIndex ); 00734 indexes << m_model->index( position.first, position.second * 2 + 1, m_rootIndex ); 00735 } 00736 else 00737 { 00738 // assumption: indexes per column == 1 00739 const qreal ipp = indexesPerPixel(); 00740 for ( int i = 0; i < ipp; ++i ) { 00741 const QModelIndex index = m_model->index( qRound( position.first * ipp ) + i, position.second, m_rootIndex ); 00742 if( index.isValid() ) 00743 indexes << index; 00744 } 00745 } 00746 return indexes; 00747 } else { 00748 return QModelIndexList(); 00749 } 00750 } 00751 00752 qreal CartesianDiagramDataCompressor::indexesPerPixel() const 00753 { 00754 if ( m_data.size() == 0 ) return 0; 00755 if ( m_data[0].size() == 0 ) return 0; 00756 if ( ! m_model ) return 0; 00757 return static_cast< qreal >( m_model->rowCount( m_rootIndex ) ) / static_cast< qreal >( m_data[0].size() ); 00758 } 00759 00760 bool CartesianDiagramDataCompressor::isValidCachePosition( const CachePosition& position ) const 00761 { 00762 if ( ! m_model ) return false; 00763 if ( m_data.size() == 0 || m_data[0].size() == 0 ) return false; 00764 if ( position.second < 0 || position.second >= m_data.size() ) return false; 00765 if ( position.first < 0 || position.first >= m_data[position.second].size() ) return false; 00766 return true; 00767 } 00768 00769 void CartesianDiagramDataCompressor::invalidate( const CachePosition& position ) 00770 { 00771 if ( isValidCachePosition( position ) ) { 00772 m_data[position.second][position.first] = DataPoint(); 00773 // Also invalidate the data value attributes at "position". 00774 // Otherwise the user overwrites the attributes without us noticing 00775 // it because we keep reading what's in the cache. 00776 m_dataValueAttributesCache.remove( position ); 00777 } 00778 } 00779 00780 bool CartesianDiagramDataCompressor::isCached( const CachePosition& position ) const 00781 { 00782 Q_ASSERT( isValidCachePosition( position ) ); 00783 const DataPoint& p = m_data[position.second][position.first]; 00784 return p.index.isValid(); 00785 } 00786 00787 void CartesianDiagramDataCompressor::calculateSampleStepWidth() 00788 { 00789 if ( m_mode == Precise ) { 00790 m_sampleStep = 1; 00791 return; 00792 } 00793 00794 static unsigned int SomePrimes[] = { 00795 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 00796 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 00797 151, 211, 313, 401, 503, 607, 701, 811, 911, 1009, 00798 10037, 12911, 16001, 20011, 50021, 00799 100003, 137867, 199999, 500009, 707753, 1000003, 0 00800 }; // ... after that, having a model at all becomes impractical 00801 00802 // we want at least 17 samples per data point, using a prime step width 00803 const double WantedSamples = 17; 00804 if ( WantedSamples > indexesPerPixel() ) { 00805 m_sampleStep = 1; 00806 } else { 00807 int i; 00808 for ( i = 0; SomePrimes[i] != 0; ++i ) { 00809 if ( WantedSamples * SomePrimes[i+1] > indexesPerPixel() ) { 00810 break; 00811 } 00812 } 00813 m_sampleStep = SomePrimes[i]; 00814 if ( SomePrimes[i] == 0 ) { 00815 m_sampleStep = SomePrimes[i-1]; 00816 } else { 00817 m_sampleStep = SomePrimes[i]; 00818 } 00819 } 00820 } 00821 00822 void CartesianDiagramDataCompressor::setDatasetDimension( int dimension ) 00823 { 00824 if ( dimension != m_datasetDimension ) { 00825 m_datasetDimension = dimension; 00826 rebuildCache(); 00827 calculateSampleStepWidth(); 00828 } 00829 }