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