KChart

KChartPlotterDiagramCompressor.cpp
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 
29 qreal calculateSlope( const PlotterDiagramCompressor::DataPoint &lhs, const PlotterDiagramCompressor::DataPoint & rhs )
30 {
31  return ( rhs.value - lhs.value ) / ( rhs.key - lhs.key );
32 }
33 
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 
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 
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 
89 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 
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 
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 
352 PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::Iterator::operator++( int )
353 {
354  Iterator result = *this;
355  ++result;
356  return result;
357 }
358 
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 
365 PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator--()
366 {
367  --m_index;
368  --m_bufferIndex;
369  return *this;
370 }
371 
372 PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::Iterator::operator--( int )
373 {
374  Iterator result = *this;
375  --result;
376  return result;
377 }
378 
379 PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator-=( int value )
380 {
381  m_index -= value;
382  return *this;
383 }
384 
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 
395 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 
400 bool PlotterDiagramCompressor::Iterator::operator!=( const PlotterDiagramCompressor::Iterator &other ) const
401 {
402  return ! ( *this == other );
403 }
404 
405 void PlotterDiagramCompressor::Iterator::invalidate()
406 {
407  m_dataset = - 1;
408 }
409 
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 
424 void PlotterDiagramCompressor::Private::setModelToZero()
425 {
426  m_model = nullptr;
427 }
428 
429 inline bool inBoundary( const QPair< qreal, qreal > &bounds, qreal value )
430 {
431  return bounds.first <= value && value <= bounds.second;
432 }
433 
434 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  {
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  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
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  {
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  emit m_parent->rowCountChanged();
694 }
695 
696 
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  emit rowCountChanged();
705  }
706 }
707 
708 void PlotterDiagramCompressor::Private::setBoundaries( const Boundaries & bound )
709 {
710  if ( bound != m_boundary )
711  {
712  m_boundary = bound;
713  emit m_parent->boundariesChanged();
714  }
715 }
716 
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 
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 
767 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 
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 
787 PlotterDiagramCompressor::PlotterDiagramCompressor(QObject *parent)
788  : QObject(parent)
789  , d( new Private( this ) )
790 {
791 }
792 
793 PlotterDiagramCompressor::~PlotterDiagramCompressor()
794 {
795  delete d;
796  d = nullptr;
797 }
798 
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  emit boundariesChanged();
811 }
812 
813 QAbstractItemModel* PlotterDiagramCompressor::model() const
814 {
815  Q_ASSERT( d );
816  return d->m_model;
817 }
818 
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 
839 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 
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  emit rowCountChanged();
865  }
866 }
867 
868 void PlotterDiagramCompressor::setMaxSlopeChange( qreal value )
869 {
870  if ( d->m_maxSlopeRadius != value )
871  {
872  d->m_maxSlopeRadius = value;
873  emit boundariesChanged();
874  }
875 }
876 
877 qreal PlotterDiagramCompressor::maxSlopeChange() const
878 {
879  return d->m_maxSlopeRadius;
880 }
881 
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 
891 int PlotterDiagramCompressor::rowCount() const
892 {
893  return d->m_model ? d->m_model->rowCount() : 0;
894 }
895 
896 void PlotterDiagramCompressor::cleanCache()
897 {
898  d->clearBuffer();
899 }
900 
901 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 
908 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 
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 
930 PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::end( int dataSet )
931 {
932  Iterator it( dataSet, this );
933  it.m_index = -1;
934  return it;
935 }
const QPair< QPointF, QPointF > calculateDataBoundaries() const override
const QPair< QPointF, QPointF > dataBoundaries() const
Return the bottom left and top right data point, that the diagram will display (unless the grid adjus...
virtual void rowsInserted(const QModelIndex &parent, int start, int end)
int width() const const
bool isValid() const const
Class only listed here to document inheritance of some KChart classes.
QPoint pos() const const
QDateTime currentDateTime()
bool isValid() const const
void boundariesChanged()
Emitted upon change of a data boundary.
Vertical
qreal toReal(bool *ok) const const
QAbstractItemModel * model() const const
Global namespace.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void destroyed(QObject *obj)
int height() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Thu Sep 24 2020 22:36:58 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.