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

© 2001 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/
Generated by doxygen 1.9.1