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

Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/

https://www.kdab.com/development-resources/qt-tools/kd-chart/