KChart

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

KDE's Doxygen guidelines are available online.