File indexing completed on 2024-12-15 04:02:33
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 "KChartPlotterDiagramCompressor.h" 0010 0011 #include "KChartPlotterDiagramCompressor_p.h" 0012 #include "KChartMath_p.h" 0013 0014 #include <QPointF> 0015 0016 using namespace KChart; 0017 0018 qreal calculateSlope( const PlotterDiagramCompressor::DataPoint &lhs, const PlotterDiagramCompressor::DataPoint & rhs ) 0019 { 0020 return ( rhs.value - lhs.value ) / ( rhs.key - lhs.key ); 0021 } 0022 0023 PlotterDiagramCompressor::Iterator::Iterator( int dataSet, PlotterDiagramCompressor *parent ) 0024 : m_parent( parent ) 0025 , m_index( 0 ) 0026 , m_dataset( dataSet ) 0027 , m_bufferIndex( 0 ) 0028 , m_rebuffer( true ) 0029 { 0030 if ( m_parent ) 0031 { 0032 if ( parent->rowCount() > m_dataset && parent->rowCount() > 0 ) 0033 { 0034 m_buffer.append( parent->data( CachePosition( m_index, m_dataset ) ) ); 0035 } 0036 } 0037 else 0038 { 0039 m_dataset = - 1; 0040 m_index = - 1; 0041 } 0042 } 0043 0044 PlotterDiagramCompressor::Iterator::Iterator( int dataSet, PlotterDiagramCompressor *parent, QVector< DataPoint > buffer ) 0045 : m_parent( parent ) 0046 , m_buffer( buffer ) 0047 , m_index( 0 ) 0048 , m_dataset( dataSet ) 0049 , m_bufferIndex( 0 ) 0050 , m_rebuffer( false ) 0051 , m_timeOfCreation( QDateTime::currentDateTime() ) 0052 { 0053 if ( !m_parent ) 0054 { 0055 m_dataset = -1 ; 0056 m_index = - 1; 0057 } 0058 else 0059 { 0060 // buffer needs to be filled 0061 if ( parent->datasetCount() > m_dataset && parent->rowCount() > 0 && m_buffer.isEmpty() ) 0062 { 0063 m_buffer.append( parent->data( CachePosition( m_index, m_dataset ) ) ); 0064 m_rebuffer = true; 0065 } 0066 } 0067 } 0068 0069 PlotterDiagramCompressor::Iterator::~Iterator() 0070 { 0071 if ( m_parent ) 0072 { 0073 if ( m_parent.data()->d->m_timeOfLastInvalidation < m_timeOfCreation ) 0074 m_parent.data()->d->m_bufferlist[ m_dataset ] = m_buffer; 0075 } 0076 } 0077 0078 bool PlotterDiagramCompressor::Iterator::isValid() const 0079 { 0080 if ( m_parent == nullptr ) 0081 return false; 0082 return m_dataset >= 0 && m_index >= 0 && m_parent.data()->rowCount() > m_index; 0083 } 0084 0085 //PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator++() 0086 //{ 0087 // ++m_index; 0088 0089 // ++m_bufferIndex; 0090 // // the version that checks dataBoundaries is separated here, this is to avoid the runtime cost 0091 // // of checking every time the boundaries if that's not necessary 0092 // if ( m_parent.data()->d->forcedBoundaries( Qt::Vertical ) || m_parent.data()->d->forcedBoundaries( Qt::Vertical ) ) 0093 // { 0094 // if ( m_bufferIndex >= m_buffer.count() && m_rebuffer ) 0095 // { 0096 // if ( m_index < m_parent.data()->rowCount() ) 0097 // { 0098 // PlotterDiagramCompressor::DataPoint dp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); 0099 // if ( m_parent.data()->d->inBoundaries( Qt::Vertical, dp ) && m_parent.data()->d->inBoundaries( Qt::Horizontal, dp ) ) 0100 // { 0101 // m_buffer.append( dp ); 0102 // } 0103 // else 0104 // { 0105 // if ( m_index + 1 < m_parent.data()->rowCount() ) 0106 // { 0107 // PlotterDiagramCompressor::DataPoint dp1 = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); 0108 // if ( m_parent.data()->d->inBoundaries( Qt::Vertical, dp1 ) && m_parent.data()->d->inBoundaries( Qt::Horizontal, dp1 ) ) 0109 // { 0110 // m_buffer.append( dp ); 0111 // } 0112 // } 0113 // } 0114 // } 0115 // } 0116 // else 0117 // { 0118 // if ( m_bufferIndex == m_buffer.count() ) 0119 // m_index = - 1; 0120 // return *this; 0121 // } 0122 // PlotterDiagramCompressor::DataPoint dp; 0123 // if ( isValid() ) 0124 // dp = m_parent.data()->data( CachePosition( m_index - 1, m_dataset ) ); 0125 // if ( m_parent ) 0126 // { 0127 // if ( m_index >= m_parent.data()->rowCount() ) 0128 // m_index = -1; 0129 // else 0130 // { 0131 // const qreal mergeRadius = m_parent.data()->d->m_mergeRadius; 0132 // PlotterDiagramCompressor::DataPoint newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); 0133 // while ( dp.distance( newdp ) <= mergeRadius 0134 // || !( m_parent.data()->d->inBoundaries( Qt::Vertical, dp ) || m_parent.data()->d->inBoundaries( Qt::Horizontal, dp ) ) ) 0135 // { 0136 // ++m_index; 0137 // if ( m_index >= m_parent.data()->rowCount() ) 0138 // { 0139 // m_index = - 1; 0140 // break; 0141 // } 0142 // newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); 0143 // } 0144 // } 0145 // } 0146 // } 0147 // else 0148 // { 0149 // // we have a new point in the buffer 0150 // if ( m_bufferIndex >= m_buffer.count() && m_rebuffer ) 0151 // { 0152 // if ( m_index < m_parent.data()->rowCount() ) 0153 // m_buffer.append( m_parent.data()->data( CachePosition( m_index, m_dataset ) ) ); 0154 // } 0155 // else 0156 // { 0157 // if ( m_bufferIndex == m_buffer.count() ) 0158 // m_index = - 1; 0159 // return *this; 0160 // } 0161 // PlotterDiagramCompressor::DataPoint dp; 0162 // if ( isValid() ) 0163 // dp = m_parent.data()->data( CachePosition( m_index - 1, m_dataset ) ); 0164 // // make sure we switch to the next point which would be in the buffer 0165 // if ( m_parent ) 0166 // { 0167 // PlotterDiagramCompressor *parent = m_parent.data(); 0168 // if ( m_index >= parent->rowCount() ) 0169 // m_index = -1; 0170 // else 0171 // { 0172 // switch ( parent->d->m_mode ) 0173 // { 0174 // case( PlotterDiagramCompressor::DISTANCE ): 0175 // { 0176 // const qreal mergeRadius = m_parent.data()->d->m_mergeRadius; 0177 // PlotterDiagramCompressor::DataPoint newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); 0178 // while ( dp.distance( newdp ) <= mergeRadius ) 0179 // { 0180 // ++m_index; 0181 // if ( m_index >= m_parent.data()->rowCount() ) 0182 // { 0183 // m_index = - 1; 0184 // break; 0185 // } 0186 // newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); 0187 // } 0188 // } 0189 // break; 0190 // case( PlotterDiagramCompressor::BOTH ): 0191 // { 0192 // const qreal mergeRadius = m_parent.data()->d->m_mergeRadius; 0193 // PlotterDiagramCompressor::DataPoint newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); 0194 // while ( dp.distance( newdp ) <= mergeRadius ) 0195 // { 0196 // ++m_index; 0197 // if ( m_index >= m_parent.data()->rowCount() ) 0198 // { 0199 // m_index = - 1; 0200 // break; 0201 // } 0202 // newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); 0203 // } 0204 // } 0205 // break; 0206 // case ( PlotterDiagramCompressor::SLOPE ): 0207 // { 0208 // const qreal mergedist = parent->d->m_maxSlopeRadius; 0209 // qreal oldSlope = 0; 0210 // qreal newSlope = 0; 0211 0212 // PlotterDiagramCompressor::DataPoint newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); 0213 // PlotterDiagramCompressor::DataPoint olddp = PlotterDiagramCompressor::DataPoint(); 0214 // if ( m_bufferIndex > 1 ) 0215 // { 0216 // oldSlope = calculateSlope( m_buffer[ m_bufferIndex - 2 ], m_buffer[ m_bufferIndex - 1 ] ); 0217 // newSlope = calculateSlope( m_buffer[ m_bufferIndex - 1 ], newdp ); 0218 // } 0219 // bool first = true; 0220 // while ( qAbs( newSlope - oldSlope ) < mergedist ) 0221 // { 0222 // ++m_index; 0223 // if ( m_index >= m_parent.data()->rowCount() ) 0224 // { 0225 // m_index = - 1; 0226 // break; 0227 // } 0228 // if ( first ) 0229 // { 0230 // oldSlope = newSlope; 0231 // first = false; 0232 // } 0233 // olddp = newdp; 0234 // newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); 0235 // newSlope = calculateSlope( olddp, newdp ); 0236 // } 0237 // } 0238 // break; 0239 // default: 0240 // Q_ASSERT( false ); 0241 // } 0242 // } 0243 // } 0244 // } 0245 // return *this; 0246 //} 0247 0248 void PlotterDiagramCompressor::Iterator::handleSlopeForward( const DataPoint &dp ) 0249 { 0250 PlotterDiagramCompressor* parent = m_parent.data(); 0251 const qreal mergedist = parent->d->m_maxSlopeRadius; 0252 qreal oldSlope = 0; 0253 qreal newSlope = 0; 0254 0255 PlotterDiagramCompressor::DataPoint newdp = dp; 0256 PlotterDiagramCompressor::DataPoint olddp = PlotterDiagramCompressor::DataPoint(); 0257 if ( m_bufferIndex > 1 ) 0258 { 0259 //oldSlope = calculateSlope( m_buffer[ m_bufferIndex - 2 ], m_buffer[ m_bufferIndex - 1 ] ); 0260 //newSlope = calculateSlope( m_buffer[ m_bufferIndex - 1 ], newdp ); 0261 oldSlope = calculateSlope( parent->data( CachePosition( m_index - 2, m_dataset ) ) , parent->data( CachePosition( m_index - 1, m_dataset ) ) ); 0262 newSlope = calculateSlope( parent->data( CachePosition( m_index - 1, m_dataset ) ), newdp ); 0263 qreal accumulatedDist = qAbs( newSlope - oldSlope ); 0264 qreal olddist = accumulatedDist; 0265 qreal newdist; 0266 int counter = 0; 0267 while ( accumulatedDist < mergedist ) 0268 { 0269 ++m_index; 0270 if ( m_index >= m_parent.data()->rowCount() ) 0271 { 0272 m_index = - 1; 0273 if ( m_buffer.last() != parent->data( CachePosition( parent->rowCount() -1, m_dataset ) ) ) 0274 m_index = parent->rowCount(); 0275 break; 0276 } 0277 oldSlope = newSlope; 0278 olddp = newdp; 0279 newdp = parent->data( CachePosition( m_index, m_dataset ) ); 0280 newSlope = calculateSlope( olddp, newdp ); 0281 newdist = qAbs( newSlope - oldSlope ); 0282 if ( olddist == newdist ) 0283 { 0284 ++counter; 0285 } 0286 else 0287 { 0288 if ( counter > 10 ) 0289 break; 0290 } 0291 accumulatedDist += newdist; 0292 olddist = newdist; 0293 } 0294 m_buffer.append( newdp ); 0295 } 0296 else 0297 m_buffer.append( dp ); 0298 } 0299 0300 PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator++() 0301 { 0302 PlotterDiagramCompressor* parent = m_parent.data(); 0303 Q_ASSERT( parent ); 0304 const int count = parent->rowCount(); 0305 //increment the indexes 0306 ++m_index; 0307 ++m_bufferIndex; 0308 //if the index reached the end of the datamodel, make this iterator an enditerator 0309 //and make sure the buffer was not already built. If that's the case it's not necessary 0310 //to rebuild it and it would be hard to extend it as we had to know where m_index was 0311 if ( m_index >= count || ( !m_rebuffer && m_bufferIndex == m_buffer.count() ) ) 0312 { 0313 if ( m_bufferIndex == m_buffer.count() ) 0314 { 0315 if ( m_buffer.last() != parent->data( CachePosition( parent->rowCount() -1, m_dataset ) ) ) 0316 m_index = parent->rowCount(); 0317 else 0318 m_index = - 1; 0319 ++m_bufferIndex; 0320 } 0321 else 0322 m_index = -1; 0323 } 0324 //if we reached the end of the buffer continue filling the buffer 0325 if ( m_bufferIndex == m_buffer.count() && m_index >= 0 && m_rebuffer ) 0326 { 0327 PlotterDiagramCompressor::DataPoint dp = parent->data( CachePosition( m_index, m_dataset ) ); 0328 if ( parent->d->inBoundaries( Qt::Vertical, dp ) && parent->d->inBoundaries( Qt::Horizontal, dp ) ) 0329 { 0330 if ( parent->d->m_mode == PlotterDiagramCompressor::SLOPE ) 0331 handleSlopeForward( dp ); 0332 } 0333 else 0334 { 0335 m_index = -1; 0336 } 0337 } 0338 return *this; 0339 } 0340 0341 PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::Iterator::operator++( int ) 0342 { 0343 Iterator result = *this; 0344 ++result; 0345 return result; 0346 } 0347 0348 PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator += ( int value ) 0349 { 0350 for ( int index = m_index; index + value != m_index; ++( *this ) ) {}; 0351 return *this; 0352 } 0353 0354 PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator--() 0355 { 0356 --m_index; 0357 --m_bufferIndex; 0358 return *this; 0359 } 0360 0361 PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::Iterator::operator--( int ) 0362 { 0363 Iterator result = *this; 0364 --result; 0365 return result; 0366 } 0367 0368 PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator-=( int value ) 0369 { 0370 m_index -= value; 0371 return *this; 0372 } 0373 0374 PlotterDiagramCompressor::DataPoint PlotterDiagramCompressor::Iterator::operator*() 0375 { 0376 if ( !m_parent ) 0377 return PlotterDiagramCompressor::DataPoint(); 0378 Q_ASSERT( m_parent ); 0379 if ( m_index == m_parent.data()->rowCount() ) 0380 return m_parent.data()->data( CachePosition( m_parent.data()->rowCount() - 1 , m_dataset ) ); 0381 return m_buffer[ m_bufferIndex ]; 0382 } 0383 0384 bool PlotterDiagramCompressor::Iterator::operator==( const PlotterDiagramCompressor::Iterator &other ) const 0385 { 0386 return m_parent.data() == other.m_parent.data() && m_index == other.m_index && m_dataset == other.m_dataset; 0387 } 0388 0389 bool PlotterDiagramCompressor::Iterator::operator!=( const PlotterDiagramCompressor::Iterator &other ) const 0390 { 0391 return ! ( *this == other ); 0392 } 0393 0394 void PlotterDiagramCompressor::Iterator::invalidate() 0395 { 0396 m_dataset = - 1; 0397 } 0398 0399 PlotterDiagramCompressor::Private::Private( PlotterDiagramCompressor *parent ) 0400 : m_parent( parent ) 0401 , m_model( nullptr ) 0402 , m_mergeRadius( 0.1 ) 0403 , m_maxSlopeRadius( 0.1 ) 0404 , m_boundary( qMakePair( QPointF( std::numeric_limits<qreal>::quiet_NaN(), std::numeric_limits<qreal>::quiet_NaN() ) 0405 , QPointF( std::numeric_limits<qreal>::quiet_NaN(), std::numeric_limits<qreal>::quiet_NaN() ) ) ) 0406 , m_forcedXBoundaries( qMakePair( std::numeric_limits<qreal>::quiet_NaN(), std::numeric_limits<qreal>::quiet_NaN() ) ) 0407 , m_forcedYBoundaries( qMakePair( std::numeric_limits<qreal>::quiet_NaN(), std::numeric_limits<qreal>::quiet_NaN() ) ) 0408 , m_mode( PlotterDiagramCompressor::SLOPE ) 0409 { 0410 0411 } 0412 0413 void PlotterDiagramCompressor::Private::setModelToZero() 0414 { 0415 m_model = nullptr; 0416 } 0417 0418 inline bool inBoundary( const QPair< qreal, qreal > &bounds, qreal value ) 0419 { 0420 return bounds.first <= value && value <= bounds.second; 0421 } 0422 0423 bool PlotterDiagramCompressor::Private::inBoundaries( Qt::Orientation orient, const PlotterDiagramCompressor::DataPoint &dp ) const 0424 { 0425 if ( orient == Qt::Vertical && forcedBoundaries( Qt::Vertical ) ) 0426 { 0427 return inBoundary( m_forcedYBoundaries, dp.value ); 0428 } 0429 else if ( forcedBoundaries( Qt::Horizontal ) ) 0430 { 0431 return inBoundary( m_forcedXBoundaries, dp.key ); 0432 } 0433 return true; 0434 } 0435 0436 #if 0 0437 // TODO this is not threadsafe do never try to invoke the painting in a different thread than this 0438 // method 0439 void PlotterDiagramCompressor::Private::rowsInserted( const QModelIndex& /*parent*/, int start, int end ) 0440 { 0441 0442 if ( m_bufferlist.count() > 0 && !m_bufferlist[ 0 ].isEmpty() && start < m_bufferlist[ 0 ].count() ) 0443 { 0444 calculateDataBoundaries(); 0445 clearBuffer(); 0446 return; 0447 } 0448 // we are handling appends only here, a prepend might be added, insert is expensive if not needed 0449 qreal minX = std::numeric_limits< qreal >::max(); 0450 qreal minY = std::numeric_limits< qreal >::max(); 0451 qreal maxX = std::numeric_limits< qreal >::min(); 0452 qreal maxY = std::numeric_limits< qreal >::min(); 0453 for ( int dataset = 0; dataset < m_bufferlist.size(); ++dataset ) 0454 { 0455 PlotterDiagramCompressor::DataPoint predecessor = m_bufferlist[ dataset ].isEmpty() ? DataPoint() : m_bufferlist[ dataset ].last(); 0456 0457 qreal oldSlope = 0; 0458 qreal newSlope = 0; 0459 PlotterDiagramCompressor::DataPoint newdp = m_parent->data( CachePosition( start, dataset ) ); 0460 PlotterDiagramCompressor::DataPoint olddp = PlotterDiagramCompressor::DataPoint(); 0461 const int datacount = m_bufferlist[ dataset ].count(); 0462 if ( m_mode != PlotterDiagramCompressor::DISTANCE && m_bufferlist[ dataset ].count() > 1 ) 0463 { 0464 oldSlope = calculateSlope( m_bufferlist[ dataset ][ datacount - 2 ], m_bufferlist[ dataset ][ datacount - 1 ] ); 0465 newSlope = calculateSlope( m_bufferlist[ dataset ][ datacount - 1 ], newdp ); 0466 } 0467 bool first = true; 0468 for ( int row = start; row <= end; ++row ) 0469 { 0470 PlotterDiagramCompressor::DataPoint curdp = m_parent->data( CachePosition( row, dataset ) ); 0471 const bool checkcur = inBoundaries( Qt::Vertical, curdp ) && inBoundaries( Qt::Horizontal, curdp ); 0472 const bool checkpred = inBoundaries( Qt::Vertical, predecessor ) && inBoundaries( Qt::Horizontal, predecessor ); 0473 const bool check = checkcur || checkpred; 0474 switch ( m_mode ) 0475 { 0476 case( PlotterDiagramCompressor::BOTH ): 0477 { 0478 if ( predecessor.distance( curdp ) > m_mergeRadius && check ) 0479 { 0480 if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() ) 0481 { 0482 m_bufferlist[ dataset ].append( curdp ); 0483 } 0484 else if ( !m_bufferlist[ dataset ].isEmpty() ) 0485 { 0486 m_bufferlist[ dataset ].insert( row, curdp ); 0487 } 0488 predecessor = curdp; 0489 minX = qMin( curdp.key, m_boundary.first.x() ); 0490 minY = qMin( curdp.value, m_boundary.first.y() ); 0491 maxX = qMax( curdp.key, m_boundary.second.x() ); 0492 maxY = qMax( curdp.value, m_boundary.second.y() ); 0493 } 0494 } 0495 break; 0496 case ( PlotterDiagramCompressor::DISTANCE ): 0497 { 0498 if ( predecessor.distance( curdp ) > m_mergeRadius && check ) 0499 { 0500 if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() ) 0501 { 0502 m_bufferlist[ dataset ].append( curdp ); 0503 } 0504 else if ( !m_bufferlist[ dataset ].isEmpty() ) 0505 { 0506 m_bufferlist[ dataset ].insert( row, curdp ); 0507 } 0508 predecessor = curdp; 0509 minX = qMin( curdp.key, m_boundary.first.x() ); 0510 minY = qMin( curdp.value, m_boundary.first.y() ); 0511 maxX = qMax( curdp.key, m_boundary.second.x() ); 0512 maxY = qMax( curdp.value, m_boundary.second.y() ); 0513 } 0514 } 0515 break; 0516 case( PlotterDiagramCompressor::SLOPE ): 0517 { 0518 if ( check && qAbs( newSlope - oldSlope ) >= m_maxSlopeRadius ) 0519 { 0520 if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() ) 0521 { 0522 m_bufferlist[ dataset ].append( curdp ); 0523 oldSlope = newSlope; 0524 } 0525 else if ( !m_bufferlist[ dataset ].isEmpty() ) 0526 { 0527 m_bufferlist[ dataset ].insert( row, curdp ); 0528 oldSlope = newSlope; 0529 } 0530 0531 predecessor = curdp; 0532 minX = qMin( curdp.key, m_boundary.first.x() ); 0533 minY = qMin( curdp.value, m_boundary.first.y() ); 0534 maxX = qMax( curdp.key, m_boundary.second.x() ); 0535 maxY = qMax( curdp.value, m_boundary.second.y() ); 0536 0537 if ( first ) 0538 { 0539 oldSlope = newSlope; 0540 first = false; 0541 } 0542 olddp = newdp; 0543 newdp = m_parent->data( CachePosition( row, dataset ) ); 0544 newSlope = calculateSlope( olddp, newdp ); 0545 } 0546 } 0547 break; 0548 } 0549 } 0550 } 0551 setBoundaries( qMakePair( QPointF( minX, minY ), QPointF( maxX, maxY ) ) ); 0552 Q_EMIT m_parent->rowCountChanged(); 0553 } 0554 #endif 0555 #include <QDebug> 0556 // TODO this is not threadsafe do never try to invoke the painting in a different thread than this 0557 // method 0558 void PlotterDiagramCompressor::Private::rowsInserted( const QModelIndex& /*parent*/, int start, int end ) 0559 { 0560 0561 //Q_ASSERT( std::numeric_limits<qreal>::quiet_NaN() < 5 || std::numeric_limits<qreal>::quiet_NaN() > 5 ); 0562 //Q_ASSERT( 5 == qMin( std::numeric_limits<qreal>::quiet_NaN(), 5.0 ) ); 0563 //Q_ASSERT( 5 == qMax( 5.0, std::numeric_limits<qreal>::quiet_NaN() ) ); 0564 if ( m_bufferlist.count() > 0 && !m_bufferlist[ 0 ].isEmpty() && start < m_bufferlist[ 0 ].count() ) 0565 { 0566 calculateDataBoundaries(); 0567 clearBuffer(); 0568 return; 0569 } 0570 0571 // we are handling appends only here, a prepend might be added, insert is expensive if not needed 0572 qreal minX = m_boundary.first.x(); 0573 qreal minY = m_boundary.first.y(); 0574 qreal maxX = m_boundary.second.x(); 0575 qreal maxY = m_boundary.second.y(); 0576 for ( int dataset = 0; dataset < m_bufferlist.size(); ++dataset ) 0577 { 0578 if ( m_mode == PlotterDiagramCompressor::SLOPE ) 0579 { 0580 PlotterDiagramCompressor::DataPoint predecessor = m_bufferlist[ dataset ].isEmpty() ? DataPoint() : m_bufferlist[ dataset ].last(); 0581 qreal oldSlope = 0; 0582 qreal newSlope = 0; 0583 int counter = 0; 0584 0585 PlotterDiagramCompressor::DataPoint newdp = m_parent->data( CachePosition( start, dataset ) ); 0586 PlotterDiagramCompressor::DataPoint olddp = PlotterDiagramCompressor::DataPoint(); 0587 if ( start > 1 ) 0588 { 0589 oldSlope = calculateSlope( m_parent->data( CachePosition( start - 2, dataset ) ), m_parent->data( CachePosition( start - 1, dataset ) ) ); 0590 olddp = m_parent->data( CachePosition( start - 1, dataset ) ); 0591 } 0592 else 0593 { 0594 m_bufferlist[ dataset ].append( newdp ); 0595 minX = qMin( minX, newdp.key ); 0596 minY = qMin( minY, newdp.value ); 0597 maxX = qMax( newdp.key, maxX ); 0598 maxY = qMax( newdp.value, maxY ); 0599 continue; 0600 } 0601 0602 qreal olddist = 0; 0603 qreal newdist = 0; 0604 for ( int row = start; row <= end; ++row ) 0605 { 0606 PlotterDiagramCompressor::DataPoint curdp = m_parent->data( CachePosition( row, dataset ) ); 0607 newdp = curdp; 0608 newSlope = calculateSlope( olddp, newdp ); 0609 olddist = newdist; 0610 newdist = qAbs( newSlope - oldSlope ); 0611 m_accumulatedDistances[ dataset ] += newdist; 0612 const bool checkcur = inBoundaries( Qt::Vertical, curdp ) && inBoundaries( Qt::Horizontal, curdp ); 0613 const bool checkpred = inBoundaries( Qt::Vertical, predecessor ) && inBoundaries( Qt::Horizontal, predecessor ); 0614 const bool check = checkcur || checkpred; 0615 0616 if ( m_accumulatedDistances[ dataset ] >= m_maxSlopeRadius && check ) 0617 { 0618 if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() ) 0619 { 0620 m_bufferlist[ dataset ].append( curdp ); 0621 } 0622 else if ( !m_bufferlist[ dataset ].isEmpty() ) 0623 { 0624 m_bufferlist[ dataset ].insert( row, curdp ); 0625 } 0626 predecessor = curdp; 0627 m_accumulatedDistances[ dataset ] = 0; 0628 } 0629 minX = qMin( minX, curdp.key ); 0630 minY = qMin( minY, curdp.value ); 0631 maxX = qMax( curdp.key, maxX ); 0632 maxY = qMax( curdp.value, maxY ); 0633 0634 oldSlope = newSlope; 0635 olddp = newdp; 0636 if ( olddist == newdist ) 0637 { 0638 ++counter; 0639 } 0640 else 0641 { 0642 if ( counter > 10 ) 0643 { 0644 m_bufferlist[ dataset ].append( curdp ); 0645 predecessor = curdp; 0646 m_accumulatedDistances[ dataset ] = 0; 0647 } 0648 } 0649 } 0650 setBoundaries( qMakePair( QPointF( minX, minY ), QPointF( maxX, maxY ) ) ); 0651 } 0652 else 0653 { 0654 PlotterDiagramCompressor::DataPoint predecessor = m_bufferlist[ dataset ].isEmpty() ? DataPoint() : m_bufferlist[ dataset ].last(); 0655 0656 for ( int row = start; row <= end; ++row ) 0657 { 0658 PlotterDiagramCompressor::DataPoint curdp = m_parent->data( CachePosition( row, dataset ) ); 0659 const bool checkcur = inBoundaries( Qt::Vertical, curdp ) && inBoundaries( Qt::Horizontal, curdp ); 0660 const bool checkpred = inBoundaries( Qt::Vertical, predecessor ) && inBoundaries( Qt::Horizontal, predecessor ); 0661 const bool check = checkcur || checkpred; 0662 if ( predecessor.distance( curdp ) > m_mergeRadius && check ) 0663 { 0664 if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() ) 0665 { 0666 m_bufferlist[ dataset ].append( curdp ); 0667 } 0668 else if ( !m_bufferlist[ dataset ].isEmpty() ) 0669 { 0670 m_bufferlist[ dataset ].insert( row, curdp ); 0671 } 0672 predecessor = curdp; 0673 qreal minX = qMin( curdp.key, m_boundary.first.x() ); 0674 qreal minY = qMin( curdp.value, m_boundary.first.y() ); 0675 qreal maxX = qMax( curdp.key, m_boundary.second.x() ); 0676 qreal maxY = qMax( curdp.value, m_boundary.second.y() ); 0677 setBoundaries( qMakePair( QPointF( minX, minY ), QPointF( maxX, maxY ) ) ); 0678 } 0679 } 0680 } 0681 } 0682 Q_EMIT m_parent->rowCountChanged(); 0683 } 0684 0685 0686 void PlotterDiagramCompressor::setCompressionModel( CompressionMode value ) 0687 { 0688 Q_ASSERT( d ); 0689 if ( d->m_mode != value ) 0690 { 0691 d->m_mode = value; 0692 d->clearBuffer(); 0693 Q_EMIT rowCountChanged(); 0694 } 0695 } 0696 0697 void PlotterDiagramCompressor::Private::setBoundaries( const Boundaries & bound ) 0698 { 0699 if ( bound != m_boundary ) 0700 { 0701 m_boundary = bound; 0702 Q_EMIT m_parent->boundariesChanged(); 0703 } 0704 } 0705 0706 void PlotterDiagramCompressor::Private::calculateDataBoundaries() 0707 { 0708 if ( !forcedBoundaries( Qt::Vertical ) || !forcedBoundaries( Qt::Horizontal ) ) 0709 { 0710 qreal minX = std::numeric_limits<qreal>::quiet_NaN(); 0711 qreal minY = std::numeric_limits<qreal>::quiet_NaN(); 0712 qreal maxX = std::numeric_limits<qreal>::quiet_NaN(); 0713 qreal maxY = std::numeric_limits<qreal>::quiet_NaN(); 0714 for ( int dataset = 0; dataset < m_parent->datasetCount(); ++dataset ) 0715 { 0716 for ( int row = 0; row < m_parent->rowCount(); ++ row ) 0717 { 0718 PlotterDiagramCompressor::DataPoint dp = m_parent->data( CachePosition( row, dataset ) ); 0719 minX = qMin( minX, dp.key ); 0720 minY = qMin( minY, dp.value ); 0721 maxX = qMax( dp.key, maxX ); 0722 maxY = qMax( dp.value, maxY ); 0723 Q_ASSERT( !ISNAN( minX ) ); 0724 Q_ASSERT( !ISNAN( minY ) ); 0725 Q_ASSERT( !ISNAN( maxX ) ); 0726 Q_ASSERT( !ISNAN( maxY ) ); 0727 } 0728 } 0729 if ( forcedBoundaries( Qt::Vertical ) ) 0730 { 0731 minY = m_forcedYBoundaries.first; 0732 maxY = m_forcedYBoundaries.second; 0733 } 0734 if ( forcedBoundaries( Qt::Horizontal ) ) 0735 { 0736 minX = m_forcedXBoundaries.first; 0737 maxX = m_forcedXBoundaries.second; 0738 } 0739 setBoundaries( qMakePair( QPointF( minX, minY ), QPointF( maxX, maxY ) ) ); 0740 } 0741 } 0742 0743 QModelIndexList PlotterDiagramCompressor::Private::mapToModel( const CachePosition &pos ) 0744 { 0745 QModelIndexList indexes; 0746 QModelIndex index; 0747 index = m_model->index( pos.first, pos.second * 2, QModelIndex() ); 0748 Q_ASSERT( index.isValid() ); 0749 indexes << index; 0750 index = m_model->index( pos.first, pos.second * 2 + 1, QModelIndex() ); 0751 Q_ASSERT( index.isValid() ); 0752 indexes << index; 0753 return indexes; 0754 } 0755 0756 bool PlotterDiagramCompressor::Private::forcedBoundaries( Qt::Orientation orient ) const 0757 { 0758 if ( orient == Qt::Vertical ) 0759 return !ISNAN( m_forcedYBoundaries.first ) && !ISNAN( m_forcedYBoundaries.second ); 0760 else 0761 return !ISNAN( m_forcedXBoundaries.first ) && !ISNAN( m_forcedXBoundaries.second ); 0762 } 0763 0764 void PlotterDiagramCompressor::Private::clearBuffer() 0765 { 0766 //TODO all iterator have to be invalid after this operation 0767 //TODO make sure there are no regressions, the timeOfLastInvalidation should stop iterators from 0768 // corrupting the cache 0769 m_bufferlist.clear(); 0770 m_bufferlist.resize( m_parent->datasetCount() ); 0771 m_accumulatedDistances.clear(); 0772 m_accumulatedDistances.resize( m_parent->datasetCount() ); 0773 m_timeOfLastInvalidation = QDateTime::currentDateTime(); 0774 } 0775 0776 PlotterDiagramCompressor::PlotterDiagramCompressor(QObject *parent) 0777 : QObject(parent) 0778 , d( new Private( this ) ) 0779 { 0780 } 0781 0782 PlotterDiagramCompressor::~PlotterDiagramCompressor() 0783 { 0784 delete d; 0785 d = nullptr; 0786 } 0787 0788 void PlotterDiagramCompressor::setForcedDataBoundaries( const QPair< qreal, qreal > &bounds, Qt::Orientation direction ) 0789 { 0790 if ( direction == Qt::Vertical ) 0791 { 0792 d->m_forcedYBoundaries = bounds; 0793 } 0794 else 0795 { 0796 d->m_forcedXBoundaries = bounds; 0797 } 0798 d->clearBuffer(); 0799 Q_EMIT boundariesChanged(); 0800 } 0801 0802 QAbstractItemModel* PlotterDiagramCompressor::model() const 0803 { 0804 Q_ASSERT( d ); 0805 return d->m_model; 0806 } 0807 0808 void PlotterDiagramCompressor::setModel( QAbstractItemModel *model ) 0809 { 0810 Q_ASSERT( d ); 0811 if ( d->m_model ) 0812 { 0813 d->m_model->disconnect( this ); 0814 d->m_model->disconnect( d ); 0815 } 0816 d->m_model = model; 0817 if ( d->m_model) 0818 { 0819 d->m_bufferlist.resize( datasetCount() ); 0820 d->m_accumulatedDistances.resize( datasetCount() ); 0821 d->calculateDataBoundaries(); 0822 connect( d->m_model, SIGNAL(rowsInserted(QModelIndex,int,int)), d, SLOT(rowsInserted(QModelIndex,int,int)) ); 0823 connect( d->m_model, SIGNAL(modelReset()), d, SLOT(clearBuffer()) ); 0824 connect( d->m_model, SIGNAL(destroyed(QObject*)), d, SLOT(setModelToZero()) ); 0825 } 0826 } 0827 0828 PlotterDiagramCompressor::DataPoint PlotterDiagramCompressor::data( const CachePosition& pos ) const 0829 { 0830 DataPoint point; 0831 QModelIndexList indexes = d->mapToModel( pos ); 0832 Q_ASSERT( indexes.count() == 2 ); 0833 QVariant yValue = d->m_model->data( indexes.last() ); 0834 QVariant xValue = d->m_model->data( indexes.first() ); 0835 Q_ASSERT( xValue.isValid() ); 0836 Q_ASSERT( yValue.isValid() ); 0837 bool ok = false; 0838 point.key = xValue.toReal( &ok ); 0839 Q_ASSERT( ok ); 0840 ok = false; 0841 point.value = yValue.toReal( &ok ); 0842 Q_ASSERT( ok ); 0843 point.index = indexes.first(); 0844 return point; 0845 } 0846 0847 void PlotterDiagramCompressor::setMergeRadius( qreal radius ) 0848 { 0849 if ( d->m_mergeRadius != radius ) 0850 { 0851 d->m_mergeRadius = radius; 0852 if ( d->m_mode != PlotterDiagramCompressor::SLOPE ) 0853 Q_EMIT rowCountChanged(); 0854 } 0855 } 0856 0857 void PlotterDiagramCompressor::setMaxSlopeChange( qreal value ) 0858 { 0859 if ( d->m_maxSlopeRadius != value ) 0860 { 0861 d->m_maxSlopeRadius = value; 0862 Q_EMIT boundariesChanged(); 0863 } 0864 } 0865 0866 qreal PlotterDiagramCompressor::maxSlopeChange() const 0867 { 0868 return d->m_maxSlopeRadius; 0869 } 0870 0871 void PlotterDiagramCompressor::setMergeRadiusPercentage( qreal radius ) 0872 { 0873 Boundaries bounds = dataBoundaries(); 0874 const qreal width = radius * ( bounds.second.x() - bounds.first.x() ); 0875 const qreal height = radius * ( bounds.second.y() - bounds.first.y() ); 0876 const qreal realRadius = std::sqrt( width * height ); 0877 setMergeRadius( realRadius ); 0878 } 0879 0880 int PlotterDiagramCompressor::rowCount() const 0881 { 0882 return d->m_model ? d->m_model->rowCount() : 0; 0883 } 0884 0885 void PlotterDiagramCompressor::cleanCache() 0886 { 0887 d->clearBuffer(); 0888 } 0889 0890 int PlotterDiagramCompressor::datasetCount() const 0891 { 0892 if ( d->m_model && d->m_model->columnCount() == 0 ) 0893 return 0; 0894 return d->m_model ? ( d->m_model->columnCount() + 1 ) / 2 : 0; 0895 } 0896 0897 QPair< QPointF, QPointF > PlotterDiagramCompressor::dataBoundaries() const 0898 { 0899 Boundaries bounds = d->m_boundary; 0900 if ( d->forcedBoundaries( Qt::Vertical ) ) 0901 { 0902 bounds.first.setY( d->m_forcedYBoundaries.first ); 0903 bounds.second.setY( d->m_forcedYBoundaries.second ); 0904 } 0905 if ( d->forcedBoundaries( Qt::Horizontal ) ) 0906 { 0907 bounds.first.setX( d->m_forcedXBoundaries.first ); 0908 bounds.second.setX( d->m_forcedXBoundaries.second ); 0909 } 0910 return bounds; 0911 } 0912 0913 PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::begin( int dataSet ) 0914 { 0915 Q_ASSERT( dataSet >= 0 && dataSet < d->m_bufferlist.count() ); 0916 return Iterator( dataSet, this, d->m_bufferlist[ dataSet ] ); 0917 } 0918 0919 PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::end( int dataSet ) 0920 { 0921 Iterator it( dataSet, this ); 0922 it.m_index = -1; 0923 return it; 0924 }