Qwt User's Guide 6.3.0
Loading...
Searching...
No Matches
qwt_point_mapper.cpp
1/******************************************************************************
2 * Qwt Widget Library
3 * Copyright (C) 1997 Josef Wilgen
4 * Copyright (C) 2002 Uwe Rathmann
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the Qwt License, Version 1.0
8 *****************************************************************************/
9
10#include "qwt_point_mapper.h"
11#include "qwt_scale_map.h"
12#include "qwt_pixel_matrix.h"
13#include "qwt_series_data.h"
14#include "qwt_math.h"
15
16#include <qpolygon.h>
17#include <qimage.h>
18#include <qpen.h>
19#include <qpainter.h>
20
21#include <qthread.h>
22#include <qfuture.h>
23#include <qtconcurrentrun.h>
24
25#if !defined( QT_NO_QFUTURE )
26#define QWT_USE_THREADS 1
27#endif
28
29static QRectF qwtInvalidRect( 0.0, 0.0, -1.0, -1.0 );
30
31static inline int qwtRoundValue( double value )
32{
33 return qRound( value );
34}
35
36static inline double qwtRoundValueF( double value )
37{
38#if 1
39 // MS Windows and at least IRIX does not have C99's nearbyint() function
40 return ( value >= 0.0 ) ? std::floor( value + 0.5 ) : std::ceil( value - 0.5 );
41#else
42 return nearbyint( value );
43#endif
44}
45
46static Qt::Orientation qwtProbeOrientation(
47 const QwtSeriesData< QPointF >* series, int from, int to )
48{
49 if ( to - from < 20 )
50 {
51 // not enough points to "have an orientation"
52 return Qt::Horizontal;
53 }
54
55 const double x0 = series->sample( from ).x();
56 const double xn = series->sample( to ).x();
57
58 if ( x0 == xn )
59 return Qt::Vertical;
60
61 const int step = ( to - from ) / 10;
62 const bool isIncreasing = xn > x0;
63
64 double x1 = x0;
65 for ( int i = from + step; i < to; i += step )
66 {
67 const double x2 = series->sample( i ).x();
68 if ( x2 != x1 )
69 {
70 if ( ( x2 > x1 ) != isIncreasing )
71 return Qt::Vertical;
72 }
73
74 x1 = x2;
75 }
76
77 return Qt::Horizontal;
78}
79
80namespace
81{
82 template< class Polygon, class Point >
83 class QwtPolygonQuadrupelX
84 {
85 public:
86 inline void start( int x, int y )
87 {
88 m_x0 = x;
89 m_y1 = m_yMin = m_yMax = m_y2 = y;
90 }
91
92 inline bool append( int x, int y )
93 {
94 if ( m_x0 != x )
95 return false;
96
97 if ( y < m_yMin )
98 m_yMin = y;
99 else if ( y > m_yMax )
100 m_yMax = y;
101
102 m_y2 = y;
103
104 return true;
105 }
106
107 inline void flush( Polygon& polyline )
108 {
109 appendTo( m_y1, polyline );
110
111 if ( m_y2 > m_y1 )
112 qSwap( m_yMin, m_yMax );
113
114 if ( m_yMax != m_y1 )
115 appendTo( m_yMax, polyline );
116
117 if ( m_yMin != m_yMax )
118 appendTo( m_yMin, polyline );
119
120 if ( m_y2 != m_yMin )
121 appendTo( m_y2, polyline );
122 }
123
124 private:
125 inline void appendTo( int y, Polygon& polyline )
126 {
127 polyline += Point( m_x0, y );
128 }
129
130 private:
131 int m_x0, m_y1, m_yMin, m_yMax, m_y2;
132 };
133
134 template< class Polygon, class Point >
135 class QwtPolygonQuadrupelY
136 {
137 public:
138 inline void start( int x, int y )
139 {
140 m_y0 = y;
141 m_x1 = m_xMin = m_xMax = m_x2 = x;
142 }
143
144 inline bool append( int x, int y )
145 {
146 if ( m_y0 != y )
147 return false;
148
149 if ( x < m_xMin )
150 m_xMin = x;
151 else if ( x > m_xMax )
152 m_xMax = x;
153
154 m_x2 = x;
155
156 return true;
157 }
158
159 inline void flush( Polygon& polyline )
160 {
161 appendTo( m_x1, polyline );
162
163 if ( m_x2 > m_x1 )
164 qSwap( m_xMin, m_xMax );
165
166 if ( m_xMax != m_x1 )
167 appendTo( m_xMax, polyline );
168
169 if ( m_xMin != m_xMax )
170 appendTo( m_xMin, polyline );
171
172 if ( m_x2 != m_xMin )
173 appendTo( m_x2, polyline );
174 }
175
176 private:
177 inline void appendTo( int x, Polygon& polyline )
178 {
179 polyline += Point( x, m_y0 );
180 }
181
182 int m_y0, m_x1, m_xMin, m_xMax, m_x2;
183 };
184}
185
186template< class Polygon, class Point, class PolygonQuadrupel >
187static Polygon qwtMapPointsQuad( const QwtScaleMap& xMap, const QwtScaleMap& yMap,
188 const QwtSeriesData< QPointF >* series, int from, int to )
189{
190 const QPointF sample0 = series->sample( from );
191
192 PolygonQuadrupel q;
193 q.start( qwtRoundValue( xMap.transform( sample0.x() ) ),
194 qwtRoundValue( yMap.transform( sample0.y() ) ) );
195
196 Polygon polyline;
197 for ( int i = from; i <= to; i++ )
198 {
199 const QPointF sample = series->sample( i );
200
201 const int x = qwtRoundValue( xMap.transform( sample.x() ) );
202 const int y = qwtRoundValue( yMap.transform( sample.y() ) );
203
204 if ( !q.append( x, y ) )
205 {
206 q.flush( polyline );
207 q.start( x, y );
208 }
209 }
210 q.flush( polyline );
211
212 return polyline;
213}
214
215template< class Polygon, class Point, class PolygonQuadrupel >
216static Polygon qwtMapPointsQuad( const Polygon& polyline )
217{
218 const int numPoints = polyline.size();
219
220 if ( numPoints < 3 )
221 return polyline;
222
223 const Point* points = polyline.constData();
224
225 Polygon polylineXY;
226
227 PolygonQuadrupel q;
228 q.start( points[0].x(), points[0].y() );
229
230 for ( int i = 0; i < numPoints; i++ )
231 {
232 const int x = points[i].x();
233 const int y = points[i].y();
234
235 if ( !q.append( x, y ) )
236 {
237 q.flush( polylineXY );
238 q.start( x, y );
239 }
240 }
241 q.flush( polylineXY );
242
243 return polylineXY;
244}
245
246
247template< class Polygon, class Point >
248static Polygon qwtMapPointsQuad( const QwtScaleMap& xMap, const QwtScaleMap& yMap,
249 const QwtSeriesData< QPointF >* series, int from, int to )
250{
251 Polygon polyline;
252 if ( from > to )
253 return polyline;
254
255 /*
256 probing some values, to decide if it is better
257 to start with x or y coordinates
258 */
259 const Qt::Orientation orientation = qwtProbeOrientation( series, from, to );
260
261 if ( orientation == Qt::Horizontal )
262 {
263 polyline = qwtMapPointsQuad< Polygon, Point,
264 QwtPolygonQuadrupelY< Polygon, Point > >( xMap, yMap, series, from, to );
265
266 polyline = qwtMapPointsQuad< Polygon, Point,
267 QwtPolygonQuadrupelX< Polygon, Point > >( polyline );
268 }
269 else
270 {
271 polyline = qwtMapPointsQuad< Polygon, Point,
272 QwtPolygonQuadrupelX< Polygon, Point > >( xMap, yMap, series, from, to );
273
274 polyline = qwtMapPointsQuad< Polygon, Point,
275 QwtPolygonQuadrupelY< Polygon, Point > >( polyline );
276 }
277
278 return polyline;
279}
280
281// Helper class to work around the 5 parameters
282// limitation of QtConcurrent::run()
283class QwtDotsCommand
284{
285 public:
286 const QwtSeriesData< QPointF >* series;
287 int from;
288 int to;
289 QRgb rgb;
290};
291
292static void qwtRenderDots(
293 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
294 const QwtDotsCommand& command, const QPoint& pos, QImage* image )
295{
296 const QRgb rgb = command.rgb;
297 QRgb* bits = reinterpret_cast< QRgb* >( image->bits() );
298
299 const int w = image->width();
300 const int h = image->height();
301
302 const int x0 = pos.x();
303 const int y0 = pos.y();
304
305 for ( int i = command.from; i <= command.to; i++ )
306 {
307 const QPointF sample = command.series->sample( i );
308
309 const int x = static_cast< int >( xMap.transform( sample.x() ) + 0.5 ) - x0;
310 const int y = static_cast< int >( yMap.transform( sample.y() ) + 0.5 ) - y0;
311
312 if ( x >= 0 && x < w && y >= 0 && y < h )
313 bits[ y * w + x ] = rgb;
314 }
315}
316
317// some functors, so that the compile can inline
318struct QwtRoundI
319{
320 inline int operator()( double value ) const
321 {
322 return qwtRoundValue( value );
323 }
324};
325
326struct QwtRoundF
327{
328 inline double operator()( double value ) const
329 {
330 return qwtRoundValueF( value );
331 }
332};
333
334struct QwtNoRoundF
335{
336 inline double operator()( double value ) const
337 {
338 return value;
339 }
340};
341
342// mapping points without any filtering - beside checking
343// the bounding rectangle
344
345template< class Polygon, class Point, class Round >
346static inline Polygon qwtToPoints(
347 const QRectF& boundingRect,
348 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
349 const QwtSeriesData< QPointF >* series,
350 int from, int to, Round round )
351{
352 Polygon polyline( to - from + 1 );
353 Point* points = polyline.data();
354
355 int numPoints = 0;
356
357 if ( boundingRect.isValid() )
358 {
359 // iterating over all values
360 // filtering out all points outside of
361 // the bounding rectangle
362
363 for ( int i = from; i <= to; i++ )
364 {
365 const QPointF sample = series->sample( i );
366
367 const double x = xMap.transform( sample.x() );
368 const double y = yMap.transform( sample.y() );
369
370 if ( boundingRect.contains( x, y ) )
371 {
372 points[ numPoints ].rx() = round( x );
373 points[ numPoints ].ry() = round( y );
374
375 numPoints++;
376 }
377 }
378
379 polyline.resize( numPoints );
380 }
381 else
382 {
383 // simply iterating over all values
384 // without any filtering
385
386 for ( int i = from; i <= to; i++ )
387 {
388 const QPointF sample = series->sample( i );
389
390 const double x = xMap.transform( sample.x() );
391 const double y = yMap.transform( sample.y() );
392
393 points[ numPoints ].rx() = round( x );
394 points[ numPoints ].ry() = round( y );
395
396 numPoints++;
397 }
398 }
399
400 return polyline;
401}
402
403static inline QPolygon qwtToPointsI(
404 const QRectF& boundingRect,
405 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
406 const QwtSeriesData< QPointF >* series,
407 int from, int to )
408{
409 return qwtToPoints< QPolygon, QPoint >(
410 boundingRect, xMap, yMap, series, from, to, QwtRoundI() );
411}
412
413template< class Round >
414static inline QPolygonF qwtToPointsF(
415 const QRectF& boundingRect,
416 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
417 const QwtSeriesData< QPointF >* series,
418 int from, int to, Round round )
419{
420 return qwtToPoints< QPolygonF, QPointF >(
421 boundingRect, xMap, yMap, series, from, to, round );
422}
423
424// Mapping points with filtering out consecutive
425// points mapped to the same position
426
427template< class Polygon, class Point, class Round >
428static inline Polygon qwtToPolylineFiltered(
429 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
430 const QwtSeriesData< QPointF >* series,
431 int from, int to, Round round )
432{
433 // in curves with many points consecutive points
434 // are often mapped to the same position. As this might
435 // result in empty lines ( or symbols hidden by others )
436 // we try to filter them out
437
438 Polygon polyline( to - from + 1 );
439 Point* points = polyline.data();
440
441 const QPointF sample0 = series->sample( from );
442
443 points[0].rx() = round( xMap.transform( sample0.x() ) );
444 points[0].ry() = round( yMap.transform( sample0.y() ) );
445
446 int pos = 0;
447 for ( int i = from + 1; i <= to; i++ )
448 {
449 const QPointF sample = series->sample( i );
450
451 const Point p( round( xMap.transform( sample.x() ) ),
452 round( yMap.transform( sample.y() ) ) );
453
454 if ( points[pos] != p )
455 points[++pos] = p;
456 }
457
458 polyline.resize( pos + 1 );
459 return polyline;
460}
461
462static inline QPolygon qwtToPolylineFilteredI(
463 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
464 const QwtSeriesData< QPointF >* series,
465 int from, int to )
466{
467 return qwtToPolylineFiltered< QPolygon, QPoint >(
468 xMap, yMap, series, from, to, QwtRoundI() );
469}
470
471template< class Round >
472static inline QPolygonF qwtToPolylineFilteredF(
473 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
474 const QwtSeriesData< QPointF >* series,
475 int from, int to, Round round )
476{
477 return qwtToPolylineFiltered< QPolygonF, QPointF >(
478 xMap, yMap, series, from, to, round );
479}
480
481template< class Polygon, class Point >
482static inline Polygon qwtToPointsFiltered(
483 const QRectF& boundingRect,
484 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
485 const QwtSeriesData< QPointF >* series, int from, int to )
486{
487 // F.e. in scatter plots ( no connecting lines ) we
488 // can sort out all duplicates ( not only consecutive points )
489
490 Polygon polygon( to - from + 1 );
491 Point* points = polygon.data();
492
493 QwtPixelMatrix pixelMatrix( boundingRect.toAlignedRect() );
494
495 int numPoints = 0;
496 for ( int i = from; i <= to; i++ )
497 {
498 const QPointF sample = series->sample( i );
499
500 const int x = qwtRoundValue( xMap.transform( sample.x() ) );
501 const int y = qwtRoundValue( yMap.transform( sample.y() ) );
502
503 if ( pixelMatrix.testAndSetPixel( x, y, true ) == false )
504 {
505 points[ numPoints ].rx() = x;
506 points[ numPoints ].ry() = y;
507
508 numPoints++;
509 }
510 }
511
512 polygon.resize( numPoints );
513 return polygon;
514}
515
516static inline QPolygon qwtToPointsFilteredI(
517 const QRectF& boundingRect,
518 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
519 const QwtSeriesData< QPointF >* series, int from, int to )
520{
521 return qwtToPointsFiltered< QPolygon, QPoint >(
522 boundingRect, xMap, yMap, series, from, to );
523}
524
525static inline QPolygonF qwtToPointsFilteredF(
526 const QRectF& boundingRect,
527 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
528 const QwtSeriesData< QPointF >* series, int from, int to )
529{
530 return qwtToPointsFiltered< QPolygonF, QPointF >(
531 boundingRect, xMap, yMap, series, from, to );
532}
533
534class QwtPointMapper::PrivateData
535{
536 public:
537 PrivateData()
538 : boundingRect( qwtInvalidRect )
539 {
540 }
541
542 QRectF boundingRect;
544};
545
548{
549 m_data = new PrivateData();
550}
551
554{
555 delete m_data;
556}
557
565{
566 m_data->flags = flags;
567}
568
574{
575 return m_data->flags;
576}
577
587{
588 if ( on )
589 m_data->flags |= flag;
590 else
591 m_data->flags &= ~flag;
592}
593
600{
601 return m_data->flags & flag;
602}
603
612void QwtPointMapper::setBoundingRect( const QRectF& rect )
613{
614 m_data->boundingRect = rect;
615}
616
622{
623 return m_data->boundingRect;
624}
625
648 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
649 const QwtSeriesData< QPointF >* series, int from, int to ) const
650{
651 QPolygonF polyline;
652
653 if ( m_data->flags & RoundPoints )
654 {
655 if ( m_data->flags & WeedOutIntermediatePoints )
656 {
657 polyline = qwtMapPointsQuad< QPolygonF, QPointF >(
658 xMap, yMap, series, from, to );
659 }
660 else if ( m_data->flags & WeedOutPoints )
661 {
662 polyline = qwtToPolylineFilteredF(
663 xMap, yMap, series, from, to, QwtRoundF() );
664 }
665 else
666 {
667 polyline = qwtToPointsF( qwtInvalidRect,
668 xMap, yMap, series, from, to, QwtRoundF() );
669 }
670 }
671 else
672 {
673 if ( m_data->flags & WeedOutPoints )
674 {
675 polyline = qwtToPolylineFilteredF(
676 xMap, yMap, series, from, to, QwtNoRoundF() );
677 }
678 else
679 {
680 polyline = qwtToPointsF( qwtInvalidRect,
681 xMap, yMap, series, from, to, QwtNoRoundF() );
682 }
683 }
684
685 return polyline;
686}
687
703 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
704 const QwtSeriesData< QPointF >* series, int from, int to ) const
705{
706 QPolygon polyline;
707
708 if ( m_data->flags & WeedOutIntermediatePoints )
709 {
710 // TODO WeedOutIntermediatePointsY ...
711 polyline = qwtMapPointsQuad< QPolygon, QPoint >(
712 xMap, yMap, series, from, to );
713 }
714 else if ( m_data->flags & WeedOutPoints )
715 {
716 polyline = qwtToPolylineFilteredI(
717 xMap, yMap, series, from, to );
718 }
719 else
720 {
721 polyline = qwtToPointsI(
722 qwtInvalidRect, xMap, yMap, series, from, to );
723 }
724
725 return polyline;
726}
727
760 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
761 const QwtSeriesData< QPointF >* series, int from, int to ) const
762{
763 QPolygonF points;
764
765 if ( m_data->flags & WeedOutPoints )
766 {
767 if ( m_data->flags & RoundPoints )
768 {
769 if ( m_data->boundingRect.isValid() )
770 {
771 points = qwtToPointsFilteredF( m_data->boundingRect,
772 xMap, yMap, series, from, to );
773 }
774 else
775 {
776 // without a bounding rectangle all we can
777 // do is to filter out duplicates of
778 // consecutive points
779
780 points = qwtToPolylineFilteredF(
781 xMap, yMap, series, from, to, QwtRoundF() );
782 }
783 }
784 else
785 {
786 // when rounding is not allowed we can't use
787 // qwtToPointsFilteredF
788
789 points = qwtToPolylineFilteredF(
790 xMap, yMap, series, from, to, QwtNoRoundF() );
791 }
792 }
793 else
794 {
795 if ( m_data->flags & RoundPoints )
796 {
797 points = qwtToPointsF( m_data->boundingRect,
798 xMap, yMap, series, from, to, QwtRoundF() );
799 }
800 else
801 {
802 points = qwtToPointsF( m_data->boundingRect,
803 xMap, yMap, series, from, to, QwtNoRoundF() );
804 }
805 }
806
807 return points;
808}
809
834 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
835 const QwtSeriesData< QPointF >* series, int from, int to ) const
836{
837 QPolygon points;
838
839 if ( m_data->flags & WeedOutPoints )
840 {
841 if ( m_data->boundingRect.isValid() )
842 {
843 points = qwtToPointsFilteredI( m_data->boundingRect,
844 xMap, yMap, series, from, to );
845 }
846 else
847 {
848 // when we don't have the bounding rectangle all
849 // we can do is to filter out consecutive duplicates
850
851 points = qwtToPolylineFilteredI(
852 xMap, yMap, series, from, to );
853 }
854 }
855 else
856 {
857 points = qwtToPointsI(
858 m_data->boundingRect, xMap, yMap, series, from, to );
859 }
860
861 return points;
862}
863
864
884 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
885 const QwtSeriesData< QPointF >* series, int from, int to,
886 const QPen& pen, bool antialiased, uint numThreads ) const
887{
888 Q_UNUSED( antialiased )
889
890#if QWT_USE_THREADS
891 if ( numThreads == 0 )
892 numThreads = QThread::idealThreadCount();
893
894 if ( numThreads <= 0 )
895 numThreads = 1;
896#else
897 Q_UNUSED( numThreads )
898#endif
899
900 // a very special optimization for scatter plots
901 // where every sample is mapped to one pixel only.
902
903 const QRect rect = m_data->boundingRect.toAlignedRect();
904
905 QImage image( rect.size(), QImage::Format_ARGB32 );
906 image.fill( Qt::transparent );
907
908 if ( pen.width() <= 1 && pen.color().alpha() == 255 )
909 {
910 QwtDotsCommand command;
911 command.series = series;
912 command.rgb = pen.color().rgba();
913
914#if QWT_USE_THREADS
915 const int numPoints = ( to - from + 1 ) / numThreads;
916
917 QList< QFuture< void > > futures;
918 for ( uint i = 0; i < numThreads; i++ )
919 {
920 const QPoint pos = rect.topLeft();
921
922 const int index0 = from + i * numPoints;
923 if ( i == numThreads - 1 )
924 {
925 command.from = index0;
926 command.to = to;
927
928 qwtRenderDots( xMap, yMap, command, pos, &image );
929 }
930 else
931 {
932 command.from = index0;
933 command.to = index0 + numPoints - 1;
934
935 futures += QtConcurrent::run( &qwtRenderDots,
936 xMap, yMap, command, pos, &image );
937 }
938 }
939 for ( int i = 0; i < futures.size(); i++ )
940 futures[i].waitForFinished();
941#else
942 command.from = from;
943 command.to = to;
944
945 qwtRenderDots( xMap, yMap, command, rect.topLeft(), &image );
946#endif
947 }
948 else
949 {
950 // fallback implementation: to be replaced later by
951 // setting the pixels of the image like above, TODO ...
952
953 QPainter painter( &image );
954 painter.setPen( pen );
955 painter.setRenderHint( QPainter::Antialiasing, antialiased );
956
957 const int chunkSize = 1000;
958 for ( int i = from; i <= to; i += chunkSize )
959 {
960 const int indexTo = qMin( i + chunkSize - 1, to );
961 const QPolygon points = toPoints(
962 xMap, yMap, series, i, indexTo );
963
964 painter.drawPoints( points );
965 }
966 }
967
968 return image;
969}
A bit field corresponding to the pixels of a rectangle.
void setBoundingRect(const QRectF &)
QRectF boundingRect() const
QPolygonF toPolygonF(const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QwtSeriesData< QPointF > *series, int from, int to) const
Translate a series of points into a QPolygonF.
TransformationFlags flags() const
QImage toImage(const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QwtSeriesData< QPointF > *series, int from, int to, const QPen &, bool antialiased, uint numThreads) const
Translate a series into a QImage.
bool testFlag(TransformationFlag) const
QPolygonF toPointsF(const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QwtSeriesData< QPointF > *series, int from, int to) const
Translate a series into a QPolygonF.
void setFlag(TransformationFlag, bool on=true)
QwtPointMapper()
Constructor.
~QwtPointMapper()
Destructor.
TransformationFlag
Flags affecting the transformation process.
@ RoundPoints
Round points to integer values.
QPolygon toPolygon(const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QwtSeriesData< QPointF > *series, int from, int to) const
Translate a series of points into a QPolygon.
void setFlags(TransformationFlags)
QFlags< TransformationFlag > TransformationFlags
QPolygon toPoints(const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QwtSeriesData< QPointF > *series, int from, int to) const
Translate a series of points into a QPolygon.
A scale map.
double transform(double s) const
virtual T sample(size_t i) const =0