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 }