1 /*
2  * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB.  All rights reserved.
3  *
4  * This file is part of the KD Chart library.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include "KChartPlotterDiagramCompressor.h"
21 
22 #include "KChartPlotterDiagramCompressor_p.h"
23 #include "KChartMath_p.h"
24 
25 #include <QPointF>
26 
27 using namespace KChart;
28 
calculateSlope(const PlotterDiagramCompressor::DataPoint & lhs,const PlotterDiagramCompressor::DataPoint & rhs)29 qreal calculateSlope( const PlotterDiagramCompressor::DataPoint &lhs, const PlotterDiagramCompressor::DataPoint & rhs )
30 {
31     return ( rhs.value - lhs.value ) /  ( rhs.key - lhs.key );
32 }
33 
Iterator(int dataSet,PlotterDiagramCompressor * parent)34 PlotterDiagramCompressor::Iterator::Iterator( int dataSet, PlotterDiagramCompressor *parent )
35     : m_parent( parent )
36     , m_index( 0 )
37     , m_dataset( dataSet )
38     , m_bufferIndex( 0 )
39     , m_rebuffer( true )
40 {
41     if ( m_parent )
42     {
43         if ( parent->rowCount() > m_dataset && parent->rowCount() > 0 )
44         {
45             m_buffer.append( parent->data( CachePosition( m_index, m_dataset ) ) );
46         }
47     }
48     else
49     {
50         m_dataset = - 1;
51         m_index = - 1;
52     }
53 }
54 
Iterator(int dataSet,PlotterDiagramCompressor * parent,QVector<DataPoint> buffer)55 PlotterDiagramCompressor::Iterator::Iterator( int dataSet, PlotterDiagramCompressor *parent, QVector< DataPoint > buffer )
56     : m_parent( parent )
57     , m_buffer( buffer )
58     , m_index( 0 )
59     , m_dataset( dataSet )
60     , m_bufferIndex( 0 )
61     , m_rebuffer( false )
62     , m_timeOfCreation( QDateTime::currentDateTime() )
63 {
64     if ( !m_parent )
65     {
66         m_dataset = -1 ;
67         m_index = - 1;
68     }
69     else
70     {
71         // buffer needs to be filled
72         if ( parent->datasetCount() > m_dataset && parent->rowCount() > 0 && m_buffer.isEmpty() )
73         {
74             m_buffer.append( parent->data( CachePosition( m_index, m_dataset ) ) );
75             m_rebuffer = true;
76         }
77     }
78 }
79 
~Iterator()80 PlotterDiagramCompressor::Iterator::~Iterator()
81 {
82     if ( m_parent )
83     {
84         if ( m_parent.data()->d->m_timeOfLastInvalidation < m_timeOfCreation )
85             m_parent.data()->d->m_bufferlist[ m_dataset ] = m_buffer;
86     }
87 }
88 
isValid() const89 bool PlotterDiagramCompressor::Iterator::isValid() const
90 {
91     if ( m_parent == nullptr )
92         return false;
93     return m_dataset >= 0 && m_index >= 0 && m_parent.data()->rowCount() > m_index;
94 }
95 
96 //PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator++()
97 //{
98 //    ++m_index;
99 
100 //    ++m_bufferIndex;
101 //    // the version that checks dataBoundaries is separated here, this is to avoid the runtime cost
102 //    // of checking every time the boundaries if thats not necessary
103 //    if ( m_parent.data()->d->forcedBoundaries( Qt::Vertical ) || m_parent.data()->d->forcedBoundaries( Qt::Vertical ) )
104 //    {
105 //        if ( m_bufferIndex >= m_buffer.count()  && m_rebuffer )
106 //        {
107 //            if ( m_index < m_parent.data()->rowCount() )
108 //            {
109 //                PlotterDiagramCompressor::DataPoint dp = m_parent.data()->data( CachePosition( m_index, m_dataset ) );
110 //                if ( m_parent.data()->d->inBoundaries( Qt::Vertical, dp ) && m_parent.data()->d->inBoundaries( Qt::Horizontal, dp ) )
111 //                {
112 //                    m_buffer.append( dp );
113 //                }
114 //                else
115 //                {
116 //                    if ( m_index + 1 < m_parent.data()->rowCount() )
117 //                    {
118 //                        PlotterDiagramCompressor::DataPoint dp1 = m_parent.data()->data( CachePosition( m_index, m_dataset ) );
119 //                        if ( m_parent.data()->d->inBoundaries( Qt::Vertical, dp1 ) && m_parent.data()->d->inBoundaries( Qt::Horizontal, dp1 ) )
120 //                        {
121 //                            m_buffer.append( dp );
122 //                        }
123 //                    }
124 //                }
125 //            }
126 //        }
127 //        else
128 //        {
129 //            if ( m_bufferIndex == m_buffer.count() )
130 //                m_index = - 1;
131 //            return *this;
132 //        }
133 //        PlotterDiagramCompressor::DataPoint dp;
134 //        if ( isValid() )
135 //            dp = m_parent.data()->data( CachePosition( m_index - 1, m_dataset ) );
136 //        if ( m_parent )
137 //        {
138 //            if ( m_index >= m_parent.data()->rowCount() )
139 //                m_index = -1;
140 //            else
141 //            {
142 //                const qreal mergeRadius = m_parent.data()->d->m_mergeRadius;
143 //                PlotterDiagramCompressor::DataPoint newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) );
144 //                while ( dp.distance( newdp ) <= mergeRadius
145 //                        || !( m_parent.data()->d->inBoundaries( Qt::Vertical, dp ) || m_parent.data()->d->inBoundaries( Qt::Horizontal, dp ) ) )
146 //                {
147 //                    ++m_index;
148 //                    if ( m_index >= m_parent.data()->rowCount() )
149 //                    {
150 //                        m_index = - 1;
151 //                        break;
152 //                    }
153 //                    newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) );
154 //                }
155 //            }
156 //        }
157 //    }
158 //    else
159 //    {
160 //        // we have a new point in the buffer
161 //        if ( m_bufferIndex >= m_buffer.count()  && m_rebuffer )
162 //        {
163 //            if ( m_index < m_parent.data()->rowCount() )
164 //                m_buffer.append( m_parent.data()->data( CachePosition( m_index, m_dataset ) ) );
165 //        }
166 //        else
167 //        {
168 //            if ( m_bufferIndex == m_buffer.count() )
169 //                m_index = - 1;
170 //            return *this;
171 //        }
172 //        PlotterDiagramCompressor::DataPoint dp;
173 //        if ( isValid() )
174 //            dp = m_parent.data()->data( CachePosition( m_index - 1, m_dataset ) );
175 //        // make sure we switch to the next point which would be in the buffer
176 //        if ( m_parent )
177 //        {
178 //            PlotterDiagramCompressor *parent = m_parent.data();
179 //            if ( m_index >= parent->rowCount() )
180 //                m_index = -1;
181 //            else
182 //            {
183 //                switch ( parent->d->m_mode )
184 //                {
185 //                case( PlotterDiagramCompressor::DISTANCE ):
186 //                    {
187 //                        const qreal mergeRadius = m_parent.data()->d->m_mergeRadius;
188 //                        PlotterDiagramCompressor::DataPoint newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) );
189 //                        while ( dp.distance( newdp ) <= mergeRadius )
190 //                        {
191 //                            ++m_index;
192 //                            if ( m_index >= m_parent.data()->rowCount() )
193 //                            {
194 //                                m_index = - 1;
195 //                                break;
196 //                            }
197 //                            newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) );
198 //                        }
199 //                    }
200 //                    break;
201 //                case( PlotterDiagramCompressor::BOTH ):
202 //                    {
203 //                        const qreal mergeRadius = m_parent.data()->d->m_mergeRadius;
204 //                        PlotterDiagramCompressor::DataPoint newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) );
205 //                        while ( dp.distance( newdp ) <= mergeRadius )
206 //                        {
207 //                            ++m_index;
208 //                            if ( m_index >= m_parent.data()->rowCount() )
209 //                            {
210 //                                m_index = - 1;
211 //                                break;
212 //                            }
213 //                            newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) );
214 //                        }
215 //                    }
216 //                    break;
217 //                case ( PlotterDiagramCompressor::SLOPE ):
218 //                    {
219 //                        const qreal mergedist = parent->d->m_maxSlopeRadius;
220 //                        qreal oldSlope = 0;
221 //                        qreal newSlope = 0;
222 
223 //                        PlotterDiagramCompressor::DataPoint newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) );
224 //                        PlotterDiagramCompressor::DataPoint olddp = PlotterDiagramCompressor::DataPoint();
225 //                        if ( m_bufferIndex > 1 )
226 //                        {
227 //                            oldSlope = calculateSlope( m_buffer[ m_bufferIndex - 2 ], m_buffer[ m_bufferIndex - 1 ] );
228 //                            newSlope = calculateSlope( m_buffer[ m_bufferIndex - 1 ], newdp );
229 //                        }
230 //                        bool first = true;
231 //                        while ( qAbs( newSlope - oldSlope ) < mergedist )
232 //                        {
233 //                            ++m_index;
234 //                            if ( m_index >= m_parent.data()->rowCount() )
235 //                            {
236 //                                m_index = - 1;
237 //                                break;
238 //                            }
239 //                            if ( first )
240 //                            {
241 //                                oldSlope = newSlope;
242 //                                first = false;
243 //                            }
244 //                            olddp = newdp;
245 //                            newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) );
246 //                            newSlope = calculateSlope( olddp, newdp );
247 //                        }
248 //                    }
249 //                    break;
250 //                default:
251 //                    Q_ASSERT( false );
252 //                }
253 //            }
254 //        }
255 //    }
256 //    return *this;
257 //}
258 
handleSlopeForward(const DataPoint & dp)259 void PlotterDiagramCompressor::Iterator::handleSlopeForward( const DataPoint &dp )
260 {
261     PlotterDiagramCompressor* parent = m_parent.data();
262     const qreal mergedist = parent->d->m_maxSlopeRadius;
263     qreal oldSlope = 0;
264     qreal newSlope = 0;
265 
266     PlotterDiagramCompressor::DataPoint newdp = dp;
267     PlotterDiagramCompressor::DataPoint olddp = PlotterDiagramCompressor::DataPoint();
268     if ( m_bufferIndex > 1 )
269     {
270         //oldSlope = calculateSlope( m_buffer[ m_bufferIndex - 2 ], m_buffer[ m_bufferIndex - 1 ] );
271         //newSlope = calculateSlope( m_buffer[ m_bufferIndex - 1 ], newdp );
272         oldSlope = calculateSlope( parent->data( CachePosition( m_index - 2, m_dataset ) ) , parent->data( CachePosition( m_index - 1, m_dataset ) ) );
273         newSlope = calculateSlope( parent->data( CachePosition( m_index - 1, m_dataset ) ), newdp );
274         qreal accumulatedDist = qAbs( newSlope - oldSlope );
275         qreal olddist = accumulatedDist;
276         qreal newdist;
277         int counter = 0;
278         while ( accumulatedDist < mergedist )
279         {
280             ++m_index;
281             if ( m_index >= m_parent.data()->rowCount() )
282             {
283                 m_index = - 1;
284                 if ( m_buffer.last() != parent->data( CachePosition( parent->rowCount() -1, m_dataset ) ) )
285                     m_index = parent->rowCount();
286                 break;
287             }
288             oldSlope = newSlope;
289             olddp = newdp;
290             newdp = parent->data( CachePosition( m_index, m_dataset ) );
291             newSlope = calculateSlope( olddp, newdp );
292             newdist = qAbs( newSlope - oldSlope );
293             if ( olddist == newdist )
294             {
295                 ++counter;
296             }
297             else
298             {
299                 if ( counter > 10 )
300                     break;
301             }
302             accumulatedDist += newdist;
303             olddist = newdist;
304         }
305         m_buffer.append( newdp );
306     }
307     else
308         m_buffer.append( dp );
309 }
310 
operator ++()311 PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator++()
312 {
313     PlotterDiagramCompressor* parent = m_parent.data();
314     Q_ASSERT( parent );
315     const int count = parent->rowCount();
316     //increment the indexes
317     ++m_index;
318     ++m_bufferIndex;
319     //if the index reached the end of the datamodel make this iterator an enditerator
320     //and make sure the buffer was not already build, if thats the case its not necessary
321     //to rebuild it and it would be hard to extend it as we had to know where m_index was
322     if ( m_index >= count || ( !m_rebuffer && m_bufferIndex == m_buffer.count() ) )
323     {
324         if ( m_bufferIndex == m_buffer.count() )
325         {
326             if ( m_buffer.last() != parent->data( CachePosition( parent->rowCount() -1, m_dataset ) ) )
327                 m_index = parent->rowCount();
328             else
329                 m_index = - 1;
330             ++m_bufferIndex;
331         }
332         else
333             m_index = -1;
334     }
335     //if we reached the end of the buffer continue filling the buffer
336     if ( m_bufferIndex == m_buffer.count() && m_index >= 0 && m_rebuffer )
337     {
338         PlotterDiagramCompressor::DataPoint dp = parent->data( CachePosition( m_index, m_dataset ) );
339         if ( parent->d->inBoundaries( Qt::Vertical, dp ) && parent->d->inBoundaries( Qt::Horizontal, dp ) )
340         {
341             if ( parent->d->m_mode == PlotterDiagramCompressor::SLOPE )
342                 handleSlopeForward( dp );
343         }
344         else
345         {
346             m_index = -1;
347         }
348     }
349     return *this;
350 }
351 
operator ++(int)352 PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::Iterator::operator++( int )
353 {
354     Iterator result = *this;
355     ++result;
356     return result;
357 }
358 
operator +=(int value)359 PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator += ( int value )
360 {
361     for ( int index = m_index; index + value != m_index; ++( *this ) ) {};
362     return *this;
363 }
364 
operator --()365 PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator--()
366 {
367     --m_index;
368     --m_bufferIndex;
369     return *this;
370 }
371 
operator --(int)372 PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::Iterator::operator--( int )
373 {
374     Iterator result = *this;
375     --result;
376     return result;
377 }
378 
operator -=(int value)379 PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator-=( int value )
380 {
381     m_index -= value;
382     return *this;
383 }
384 
operator *()385 PlotterDiagramCompressor::DataPoint PlotterDiagramCompressor::Iterator::operator*()
386 {
387     if ( !m_parent )
388         return PlotterDiagramCompressor::DataPoint();
389     Q_ASSERT( m_parent );
390     if ( m_index == m_parent.data()->rowCount() )
391         return m_parent.data()->data( CachePosition( m_parent.data()->rowCount() - 1 , m_dataset ) );
392     return m_buffer[ m_bufferIndex ];
393 }
394 
operator ==(const PlotterDiagramCompressor::Iterator & other) const395 bool PlotterDiagramCompressor::Iterator::operator==( const PlotterDiagramCompressor::Iterator &other ) const
396 {
397     return m_parent.data() == other.m_parent.data() && m_index == other.m_index && m_dataset == other.m_dataset;
398 }
399 
operator !=(const PlotterDiagramCompressor::Iterator & other) const400 bool PlotterDiagramCompressor::Iterator::operator!=( const PlotterDiagramCompressor::Iterator &other ) const
401 {
402     return ! ( *this == other );
403 }
404 
invalidate()405 void PlotterDiagramCompressor::Iterator::invalidate()
406 {
407     m_dataset = - 1;
408 }
409 
Private(PlotterDiagramCompressor * parent)410 PlotterDiagramCompressor::Private::Private( PlotterDiagramCompressor *parent )
411     : m_parent( parent )
412     , m_model( nullptr )
413     , m_mergeRadius( 0.1 )
414     , m_maxSlopeRadius( 0.1 )
415     , m_boundary( qMakePair( QPointF( std::numeric_limits<qreal>::quiet_NaN(), std::numeric_limits<qreal>::quiet_NaN() )
416                                       , QPointF( std::numeric_limits<qreal>::quiet_NaN(), std::numeric_limits<qreal>::quiet_NaN() ) ) )
417     , m_forcedXBoundaries( qMakePair( std::numeric_limits<qreal>::quiet_NaN(), std::numeric_limits<qreal>::quiet_NaN() ) )
418     , m_forcedYBoundaries( qMakePair( std::numeric_limits<qreal>::quiet_NaN(), std::numeric_limits<qreal>::quiet_NaN() ) )
419     , m_mode( PlotterDiagramCompressor::SLOPE )
420 {
421 
422 }
423 
setModelToZero()424 void PlotterDiagramCompressor::Private::setModelToZero()
425 {
426     m_model = nullptr;
427 }
428 
inBoundary(const QPair<qreal,qreal> & bounds,qreal value)429 inline bool inBoundary( const QPair< qreal, qreal > &bounds, qreal value )
430 {
431     return bounds.first <= value && value <= bounds.second;
432 }
433 
inBoundaries(Qt::Orientation orient,const PlotterDiagramCompressor::DataPoint & dp) const434 bool PlotterDiagramCompressor::Private::inBoundaries( Qt::Orientation orient, const PlotterDiagramCompressor::DataPoint &dp ) const
435 {
436     if ( orient == Qt::Vertical && forcedBoundaries( Qt::Vertical ) )
437     {
438         return inBoundary( m_forcedYBoundaries, dp.value );
439     }
440     else if ( forcedBoundaries( Qt::Horizontal ) )
441     {
442         return inBoundary( m_forcedXBoundaries, dp.key );
443     }
444     return true;
445 }
446 
447 #if 0
448 // TODO this is not threadsafe do never try to invoke the painting in a different thread than this
449 // method
450 void PlotterDiagramCompressor::Private::rowsInserted( const QModelIndex& /*parent*/, int start, int end )
451 {
452 
453    if ( m_bufferlist.count() > 0 && !m_bufferlist[ 0 ].isEmpty() && start < m_bufferlist[ 0 ].count() )
454    {
455        calculateDataBoundaries();
456        clearBuffer();
457        return;
458    }
459    // we are handling appends only here, a prepend might be added, insert is expensive if not needed
460    qreal minX = std::numeric_limits< qreal >::max();
461    qreal minY = std::numeric_limits< qreal >::max();
462    qreal maxX = std::numeric_limits< qreal >::min();
463    qreal maxY = std::numeric_limits< qreal >::min();
464    for ( int dataset = 0; dataset < m_bufferlist.size(); ++dataset )
465    {
466        PlotterDiagramCompressor::DataPoint predecessor = m_bufferlist[ dataset ].isEmpty() ? DataPoint() : m_bufferlist[ dataset ].last();
467 
468        qreal oldSlope = 0;
469        qreal newSlope = 0;
470        PlotterDiagramCompressor::DataPoint newdp = m_parent->data( CachePosition( start, dataset ) );
471        PlotterDiagramCompressor::DataPoint olddp = PlotterDiagramCompressor::DataPoint();
472        const int datacount = m_bufferlist[ dataset ].count();
473        if ( m_mode != PlotterDiagramCompressor::DISTANCE && m_bufferlist[ dataset ].count() > 1 )
474        {
475            oldSlope = calculateSlope( m_bufferlist[ dataset ][ datacount - 2 ], m_bufferlist[ dataset ][ datacount - 1 ] );
476            newSlope = calculateSlope( m_bufferlist[ dataset ][ datacount - 1 ], newdp );
477        }
478        bool first = true;
479        for ( int row = start; row <= end; ++row )
480        {
481            PlotterDiagramCompressor::DataPoint curdp = m_parent->data( CachePosition( row, dataset ) );
482            const bool checkcur = inBoundaries( Qt::Vertical, curdp ) && inBoundaries( Qt::Horizontal, curdp );
483            const bool checkpred = inBoundaries( Qt::Vertical, predecessor ) && inBoundaries( Qt::Horizontal, predecessor );
484            const bool check = checkcur || checkpred;
485            switch ( m_mode )
486            {
487            case( PlotterDiagramCompressor::BOTH ):
488                {
489                    if ( predecessor.distance( curdp ) > m_mergeRadius && check )
490                    {
491                        if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() )
492                        {
493                            m_bufferlist[ dataset ].append( curdp );
494                        }
495                        else if ( !m_bufferlist[ dataset ].isEmpty() )
496                        {
497                            m_bufferlist[ dataset ].insert( row, curdp );
498                        }
499                        predecessor = curdp;
500                        minX = qMin( curdp.key, m_boundary.first.x() );
501                        minY = qMin( curdp.value, m_boundary.first.y() );
502                        maxX = qMax( curdp.key, m_boundary.second.x() );
503                        maxY = qMax( curdp.value, m_boundary.second.y() );
504                    }
505                }
506                break;
507            case ( PlotterDiagramCompressor::DISTANCE ):
508                {
509                    if ( predecessor.distance( curdp ) > m_mergeRadius && check )
510                    {
511                        if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() )
512                        {
513                            m_bufferlist[ dataset ].append( curdp );
514                        }
515                        else if ( !m_bufferlist[ dataset ].isEmpty() )
516                        {
517                            m_bufferlist[ dataset ].insert( row, curdp );
518                        }
519                        predecessor = curdp;
520                        minX = qMin( curdp.key, m_boundary.first.x() );
521                        minY = qMin( curdp.value, m_boundary.first.y() );
522                        maxX = qMax( curdp.key, m_boundary.second.x() );
523                        maxY = qMax( curdp.value, m_boundary.second.y() );
524                    }
525                }
526                break;
527            case( PlotterDiagramCompressor::SLOPE ):
528                {
529                    if ( check && qAbs( newSlope - oldSlope ) >= m_maxSlopeRadius )
530                    {
531                        if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() )
532                        {
533                            m_bufferlist[ dataset ].append( curdp );
534                            oldSlope = newSlope;
535                        }
536                        else if ( !m_bufferlist[ dataset ].isEmpty() )
537                        {
538                            m_bufferlist[ dataset ].insert( row, curdp );
539                            oldSlope = newSlope;
540                        }
541 
542                        predecessor = curdp;
543                        minX = qMin( curdp.key, m_boundary.first.x() );
544                        minY = qMin( curdp.value, m_boundary.first.y() );
545                        maxX = qMax( curdp.key, m_boundary.second.x() );
546                        maxY = qMax( curdp.value, m_boundary.second.y() );
547 
548                        if ( first )
549                        {
550                            oldSlope = newSlope;
551                            first = false;
552                        }
553                        olddp = newdp;
554                        newdp = m_parent->data( CachePosition( row, dataset ) );
555                        newSlope = calculateSlope( olddp, newdp );
556                    }
557                }
558                break;
559            }
560        }
561    }
562    setBoundaries( qMakePair( QPointF( minX, minY ), QPointF( maxX, maxY ) ) );
563    Q_EMIT m_parent->rowCountChanged();
564 }
565 #endif
566 #include <QDebug>
567 // TODO this is not threadsafe do never try to invoke the painting in a different thread than this
568 // method
rowsInserted(const QModelIndex &,int start,int end)569 void PlotterDiagramCompressor::Private::rowsInserted( const QModelIndex& /*parent*/, int start, int end )
570 {
571 
572     //Q_ASSERT( std::numeric_limits<qreal>::quiet_NaN() < 5 || std::numeric_limits<qreal>::quiet_NaN() > 5 );
573     //Q_ASSERT( 5 == qMin( std::numeric_limits<qreal>::quiet_NaN(),  5.0 ) );
574     //Q_ASSERT( 5 == qMax( 5.0, std::numeric_limits<qreal>::quiet_NaN() ) );
575     if ( m_bufferlist.count() > 0 && !m_bufferlist[ 0 ].isEmpty() && start < m_bufferlist[ 0 ].count() )
576     {
577         calculateDataBoundaries();
578         clearBuffer();
579         return;
580     }
581 
582     // we are handling appends only here, a prepend might be added, insert is expensive if not needed
583     qreal minX = m_boundary.first.x();
584     qreal minY = m_boundary.first.y();
585     qreal maxX = m_boundary.second.x();
586     qreal maxY = m_boundary.second.y();
587     for ( int dataset = 0; dataset < m_bufferlist.size(); ++dataset )
588     {
589         if ( m_mode == PlotterDiagramCompressor::SLOPE )
590         {
591             PlotterDiagramCompressor::DataPoint predecessor = m_bufferlist[ dataset ].isEmpty() ? DataPoint() : m_bufferlist[ dataset ].last();
592             qreal oldSlope = 0;
593             qreal newSlope = 0;
594             int counter = 0;
595 
596             PlotterDiagramCompressor::DataPoint newdp = m_parent->data( CachePosition( start, dataset ) );
597             PlotterDiagramCompressor::DataPoint olddp = PlotterDiagramCompressor::DataPoint();
598             if ( start  > 1 )
599             {
600                 oldSlope = calculateSlope( m_parent->data( CachePosition( start - 2, dataset ) ), m_parent->data( CachePosition( start - 1, dataset ) ) );
601                 olddp = m_parent->data( CachePosition( start - 1, dataset ) );
602             }
603             else
604             {
605                 m_bufferlist[ dataset ].append( newdp );
606                 minX = qMin( minX, newdp.key );
607                 minY = qMin( minY, newdp.value );
608                 maxX = qMax( newdp.key, maxX );
609                 maxY = qMax( newdp.value, maxY );
610                 continue;
611             }
612 
613             qreal olddist = 0;
614             qreal newdist = 0;
615             for ( int row = start; row <= end; ++row )
616             {
617                 PlotterDiagramCompressor::DataPoint curdp = m_parent->data( CachePosition( row, dataset ) );
618                 newdp = curdp;
619                 newSlope = calculateSlope( olddp, newdp );
620                 olddist = newdist;
621                 newdist = qAbs( newSlope - oldSlope );
622                 m_accumulatedDistances[ dataset ] += newdist;
623                 const bool checkcur = inBoundaries( Qt::Vertical, curdp ) && inBoundaries( Qt::Horizontal, curdp );
624                 const bool checkpred = inBoundaries( Qt::Vertical, predecessor ) && inBoundaries( Qt::Horizontal, predecessor );
625                 const bool check = checkcur || checkpred;
626 
627                 if ( m_accumulatedDistances[ dataset ] >= m_maxSlopeRadius && check )
628                 {
629                     if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() )
630                     {
631                         m_bufferlist[ dataset ].append( curdp );
632                     }
633                     else if ( !m_bufferlist[ dataset ].isEmpty() )
634                     {
635                         m_bufferlist[ dataset ].insert( row, curdp );
636                     }
637                     predecessor = curdp;
638                     m_accumulatedDistances[ dataset ] = 0;
639                 }
640                 minX = qMin( minX, curdp.key );
641                 minY = qMin( minY, curdp.value );
642                 maxX = qMax( curdp.key, maxX );
643                 maxY = qMax( curdp.value, maxY );
644 
645                 oldSlope = newSlope;
646                 olddp = newdp;
647                 if ( olddist == newdist )
648                 {
649                     ++counter;
650                 }
651                 else
652                 {
653                     if ( counter > 10 )
654                     {
655                         m_bufferlist[ dataset ].append( curdp );
656                         predecessor = curdp;
657                         m_accumulatedDistances[ dataset ] = 0;
658                     }
659                 }
660             }
661             setBoundaries( qMakePair( QPointF( minX, minY ), QPointF( maxX, maxY ) ) );
662         }
663         else
664         {
665         PlotterDiagramCompressor::DataPoint predecessor = m_bufferlist[ dataset ].isEmpty() ? DataPoint() : m_bufferlist[ dataset ].last();
666 
667         for ( int row = start; row <= end; ++row )
668         {
669             PlotterDiagramCompressor::DataPoint curdp = m_parent->data( CachePosition( row, dataset ) );
670             const bool checkcur = inBoundaries( Qt::Vertical, curdp ) && inBoundaries( Qt::Horizontal, curdp );
671             const bool checkpred = inBoundaries( Qt::Vertical, predecessor ) && inBoundaries( Qt::Horizontal, predecessor );
672             const bool check = checkcur || checkpred;
673             if ( predecessor.distance( curdp ) > m_mergeRadius && check )
674             {
675                 if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() )
676                 {
677                     m_bufferlist[ dataset ].append( curdp );
678                 }
679                 else if ( !m_bufferlist[ dataset ].isEmpty() )
680                 {
681                     m_bufferlist[ dataset ].insert( row, curdp );
682                 }
683                 predecessor = curdp;
684                 qreal minX = qMin( curdp.key, m_boundary.first.x() );
685                 qreal minY = qMin( curdp.value, m_boundary.first.y() );
686                 qreal maxX = qMax( curdp.key, m_boundary.second.x() );
687                 qreal maxY = qMax( curdp.value, m_boundary.second.y() );
688                 setBoundaries( qMakePair( QPointF( minX, minY ), QPointF( maxX, maxY ) ) );
689             }
690         }
691         }
692     }
693     Q_EMIT m_parent->rowCountChanged();
694 }
695 
696 
setCompressionModel(CompressionMode value)697 void PlotterDiagramCompressor::setCompressionModel( CompressionMode value )
698 {
699     Q_ASSERT( d );
700     if ( d->m_mode != value )
701     {
702         d->m_mode = value;
703         d->clearBuffer();
704         Q_EMIT rowCountChanged();
705     }
706 }
707 
setBoundaries(const Boundaries & bound)708 void PlotterDiagramCompressor::Private::setBoundaries( const Boundaries & bound )
709 {
710     if ( bound != m_boundary )
711     {
712         m_boundary = bound;
713         Q_EMIT m_parent->boundariesChanged();
714     }
715 }
716 
calculateDataBoundaries()717 void PlotterDiagramCompressor::Private::calculateDataBoundaries()
718 {
719     if ( !forcedBoundaries( Qt::Vertical ) || !forcedBoundaries( Qt::Horizontal ) )
720     {
721         qreal minX = std::numeric_limits<qreal>::quiet_NaN();
722         qreal minY = std::numeric_limits<qreal>::quiet_NaN();
723         qreal maxX = std::numeric_limits<qreal>::quiet_NaN();
724         qreal maxY = std::numeric_limits<qreal>::quiet_NaN();
725         for ( int dataset = 0; dataset < m_parent->datasetCount(); ++dataset )
726         {
727             for ( int row = 0; row < m_parent->rowCount(); ++ row )
728             {
729                 PlotterDiagramCompressor::DataPoint dp = m_parent->data( CachePosition( row, dataset ) );
730                 minX = qMin( minX, dp.key );
731                 minY = qMin( minY, dp.value );
732                 maxX = qMax( dp.key, maxX );
733                 maxY = qMax( dp.value, maxY );
734                 Q_ASSERT( !ISNAN( minX ) );
735                 Q_ASSERT( !ISNAN( minY ) );
736                 Q_ASSERT( !ISNAN( maxX ) );
737                 Q_ASSERT( !ISNAN( maxY ) );
738             }
739         }
740         if ( forcedBoundaries( Qt::Vertical ) )
741         {
742             minY = m_forcedYBoundaries.first;
743             maxY = m_forcedYBoundaries.second;
744         }
745         if ( forcedBoundaries( Qt::Horizontal ) )
746         {
747             minX = m_forcedXBoundaries.first;
748             maxX = m_forcedXBoundaries.second;
749         }
750         setBoundaries( qMakePair( QPointF( minX, minY ), QPointF( maxX, maxY ) ) );
751     }
752 }
753 
mapToModel(const CachePosition & pos)754 QModelIndexList PlotterDiagramCompressor::Private::mapToModel( const CachePosition &pos )
755 {
756     QModelIndexList indexes;
757     QModelIndex index;
758     index = m_model->index( pos.first, pos.second * 2, QModelIndex() );
759     Q_ASSERT( index.isValid() );
760     indexes << index;
761     index = m_model->index( pos.first, pos.second * 2 + 1, QModelIndex() );
762     Q_ASSERT( index.isValid() );
763     indexes << index;
764     return indexes;
765 }
766 
forcedBoundaries(Qt::Orientation orient) const767 bool PlotterDiagramCompressor::Private::forcedBoundaries( Qt::Orientation orient ) const
768 {
769     if ( orient == Qt::Vertical )
770         return !ISNAN( m_forcedYBoundaries.first ) && !ISNAN( m_forcedYBoundaries.second );
771     else
772         return !ISNAN( m_forcedXBoundaries.first ) && !ISNAN( m_forcedXBoundaries.second );
773 }
774 
clearBuffer()775 void PlotterDiagramCompressor::Private::clearBuffer()
776 {
777     //TODO all iterator have to be invalid after this operation
778     //TODO make sure there are no regressions, the timeOfLastInvalidation should stop iterators from
779     // corrupting the cache
780     m_bufferlist.clear();
781     m_bufferlist.resize( m_parent->datasetCount() );
782     m_accumulatedDistances.clear();
783     m_accumulatedDistances.resize( m_parent->datasetCount() );
784     m_timeOfLastInvalidation = QDateTime::currentDateTime();
785 }
786 
PlotterDiagramCompressor(QObject * parent)787 PlotterDiagramCompressor::PlotterDiagramCompressor(QObject *parent)
788     : QObject(parent)
789     , d( new Private( this ) )
790 {
791 }
792 
~PlotterDiagramCompressor()793 PlotterDiagramCompressor::~PlotterDiagramCompressor()
794 {
795     delete d;
796     d = nullptr;
797 }
798 
setForcedDataBoundaries(const QPair<qreal,qreal> & bounds,Qt::Orientation direction)799 void PlotterDiagramCompressor::setForcedDataBoundaries( const QPair< qreal, qreal > &bounds, Qt::Orientation direction )
800 {
801     if ( direction == Qt::Vertical )
802     {
803         d->m_forcedYBoundaries = bounds;
804     }
805     else
806     {
807         d->m_forcedXBoundaries = bounds;
808     }
809     d->clearBuffer();
810     Q_EMIT boundariesChanged();
811 }
812 
model() const813 QAbstractItemModel* PlotterDiagramCompressor::model() const
814 {
815     Q_ASSERT( d );
816     return d->m_model;
817 }
818 
setModel(QAbstractItemModel * model)819 void PlotterDiagramCompressor::setModel( QAbstractItemModel *model )
820 {
821     Q_ASSERT( d );
822     if ( d->m_model )
823     {
824         d->m_model->disconnect( this );
825         d->m_model->disconnect( d );
826     }
827     d->m_model = model;
828     if ( d->m_model)
829     {
830         d->m_bufferlist.resize( datasetCount() );
831         d->m_accumulatedDistances.resize( datasetCount() );
832         d->calculateDataBoundaries();
833         connect( d->m_model, SIGNAL(rowsInserted(QModelIndex,int,int)), d, SLOT(rowsInserted(QModelIndex,int,int)) );
834         connect( d->m_model, SIGNAL(modelReset()), d, SLOT(clearBuffer()) );
835         connect( d->m_model, SIGNAL(destroyed(QObject*)), d, SLOT(setModelToZero()) );
836     }
837 }
838 
data(const CachePosition & pos) const839 PlotterDiagramCompressor::DataPoint PlotterDiagramCompressor::data( const CachePosition& pos ) const
840 {
841     DataPoint point;
842     QModelIndexList indexes = d->mapToModel( pos );
843     Q_ASSERT( indexes.count() == 2 );
844     QVariant yValue = d->m_model->data( indexes.last() );
845     QVariant xValue = d->m_model->data( indexes.first() );
846     Q_ASSERT( xValue.isValid() );
847     Q_ASSERT( yValue.isValid() );
848     bool ok = false;
849     point.key = xValue.toReal( &ok );
850     Q_ASSERT( ok );
851     ok = false;
852     point.value = yValue.toReal( &ok );
853     Q_ASSERT( ok );
854     point.index = indexes.first();
855     return point;
856 }
857 
setMergeRadius(qreal radius)858 void PlotterDiagramCompressor::setMergeRadius( qreal radius )
859 {
860     if ( d->m_mergeRadius != radius )
861     {
862         d->m_mergeRadius = radius;
863         if ( d->m_mode != PlotterDiagramCompressor::SLOPE )
864             Q_EMIT rowCountChanged();
865     }
866 }
867 
setMaxSlopeChange(qreal value)868 void PlotterDiagramCompressor::setMaxSlopeChange( qreal value )
869 {
870     if ( d->m_maxSlopeRadius != value )
871     {
872         d->m_maxSlopeRadius = value;
873         Q_EMIT boundariesChanged();
874     }
875 }
876 
maxSlopeChange() const877 qreal PlotterDiagramCompressor::maxSlopeChange() const
878 {
879     return d->m_maxSlopeRadius;
880 }
881 
setMergeRadiusPercentage(qreal radius)882 void PlotterDiagramCompressor::setMergeRadiusPercentage( qreal radius )
883 {
884     Boundaries bounds = dataBoundaries();
885     const qreal width = radius * ( bounds.second.x() - bounds.first.x() );
886     const qreal height = radius * ( bounds.second.y() - bounds.first.y() );
887     const qreal realRadius = std::sqrt( width * height );
888     setMergeRadius( realRadius );
889 }
890 
rowCount() const891 int PlotterDiagramCompressor::rowCount() const
892 {
893     return d->m_model ? d->m_model->rowCount() : 0;
894 }
895 
cleanCache()896 void PlotterDiagramCompressor::cleanCache()
897 {
898     d->clearBuffer();
899 }
900 
datasetCount() const901 int PlotterDiagramCompressor::datasetCount() const
902 {
903     if ( d->m_model && d->m_model->columnCount() == 0 )
904         return 0;
905     return d->m_model ? ( d->m_model->columnCount() + 1 ) / 2  : 0;
906 }
907 
dataBoundaries() const908 QPair< QPointF, QPointF > PlotterDiagramCompressor::dataBoundaries() const
909 {
910     Boundaries bounds = d->m_boundary;
911     if ( d->forcedBoundaries( Qt::Vertical ) )
912     {
913         bounds.first.setY( d->m_forcedYBoundaries.first );
914         bounds.second.setY( d->m_forcedYBoundaries.second );
915     }
916     if ( d->forcedBoundaries( Qt::Horizontal ) )
917     {
918         bounds.first.setX( d->m_forcedXBoundaries.first );
919         bounds.second.setX( d->m_forcedXBoundaries.second );
920     }
921     return bounds;
922 }
923 
begin(int dataSet)924 PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::begin( int dataSet )
925 {
926     Q_ASSERT( dataSet >= 0 && dataSet < d->m_bufferlist.count() );
927     return Iterator( dataSet, this, d->m_bufferlist[ dataSet ] );
928 }
929 
end(int dataSet)930 PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::end( int dataSet )
931 {
932     Iterator it( dataSet, this );
933     it.m_index = -1;
934     return it;
935 }
936