Qwt User's Guide 6.3.0
Loading...
Searching...
No Matches
qwt_plot_vectorfield.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_plot_vectorfield.h"
11#include "qwt_vectorfield_symbol.h"
12#include "qwt_scale_map.h"
13#include "qwt_color_map.h"
14#include "qwt_painter.h"
15#include "qwt_text.h"
16#include "qwt_graphic.h"
17#include "qwt_math.h"
18
19#include <qpainter.h>
20#include <qpainterpath.h>
21#include <qdebug.h>
22#include <cstdlib>
23#include <limits>
24
25#define DEBUG_RENDER 0
26
27#if DEBUG_RENDER
28#include <qelapsedtimer.h>
29#endif
30
31
32static inline double qwtVector2Radians( double vx, double vy )
33{
34 if ( vx == 0.0 )
35 return ( vy >= 0 ) ? M_PI_2 : 3 * M_PI_2;
36
37 return std::atan2( vy, vx );
38}
39
40static inline double qwtVector2Magnitude( double vx, double vy )
41{
42 return sqrt( vx * vx + vy * vy );
43}
44
45static QwtInterval qwtMagnitudeRange(
47{
48 if ( series->size() == 0 )
49 return QwtInterval( 0, 1 );
50
51 const QwtVectorFieldSample s0 = series->sample( 0 );
52
53 double min = s0.vx * s0.vx + s0.vy * s0.vy;
54 double max = min;
55
56 for ( uint i = 1; i < series->size(); i++ )
57 {
58 const QwtVectorFieldSample s = series->sample( i );
59 const double l = s.vx * s.vx + s.vy * s.vy;
60
61 if ( l < min )
62 min = l;
63
64 if ( l > max )
65 max = l;
66 }
67
68 min = std::sqrt( min );
69 max = std::sqrt( max );
70
71 if ( max == min )
72 max += 1.0;
73
74 return QwtInterval( min, max );
75}
76
77static inline QTransform qwtSymbolTransformation(
78 const QTransform& oldTransform, double x, double y,
79 double vx, double vy, double magnitude )
80{
81 QTransform transform = oldTransform;
82
83 if ( !transform.isIdentity() )
84 {
85 transform.translate( x, y );
86
87 const double radians = qwtVector2Radians( vx, vy );
88 transform.rotateRadians( radians );
89 }
90 else
91 {
92 /*
93 When starting with no transformation ( f.e on screen )
94 the matrix can be found without having to use
95 trigonometric functions
96 */
97
98 qreal sin, cos;
99 if ( magnitude == 0.0 )
100 {
101 // something
102 sin = 1.0;
103 cos = 0.0;
104 }
105 else
106 {
107 sin = vy / magnitude;
108 cos = vx / magnitude;
109 }
110
111 transform.setMatrix( cos, sin, 0.0, -sin, cos, 0.0, x, y, 1.0 );
112 }
113
114 return transform;
115}
116
117namespace
118{
119 class FilterMatrix
120 {
121 public:
122 class Entry
123 {
124 public:
125 inline void addSample( double sx, double sy,
126 double svx, double svy )
127 {
128 x += sx;
129 y += sy;
130
131 vx += svx;
132 vy += svy;
133
134 count++;
135 }
136
137 quint32 count;
138
139 // screen positions -> float is good enough
140 float x;
141 float y;
142 float vx;
143 float vy;
144 };
145
146 FilterMatrix( const QRectF& dataRect,
147 const QRectF& canvasRect, const QSizeF& cellSize )
148 {
149 m_dx = cellSize.width();
150 m_dy = cellSize.height();
151
152 m_x0 = dataRect.x();
153 if ( m_x0 < canvasRect.x() )
154 m_x0 += int( ( canvasRect.x() - m_x0 ) / m_dx ) * m_dx;
155
156 m_y0 = dataRect.y();
157 if ( m_y0 < canvasRect.y() )
158 m_y0 += int( ( canvasRect.y() - m_y0 ) / m_dy ) * m_dy;
159
160 m_numColumns = canvasRect.width() / m_dx + 1;
161 m_numRows = canvasRect.height() / m_dy + 1;
162
163#if 1
164 /*
165 limit column and row count to a maximum of 1000000,
166 so that memory usage is not an issue
167 */
168 if ( m_numColumns > 1000 )
169 {
170 m_dx = canvasRect.width() / 1000;
171 m_numColumns = canvasRect.width() / m_dx + 1;
172 }
173
174 if ( m_numRows > 1000 )
175 {
176 m_dy = canvasRect.height() / 1000;
177 m_numRows = canvasRect.height() / m_dx + 1;
178 }
179#endif
180
181 m_x1 = m_x0 + m_numColumns * m_dx;
182 m_y1 = m_y0 + m_numRows * m_dy;
183
184 m_entries = ( Entry* )::calloc( m_numRows * m_numColumns, sizeof( Entry ) );
185 if ( m_entries == NULL )
186 {
187 qWarning() << "QwtPlotVectorField: raster for filtering too fine - running out of memory";
188 }
189 }
190
191 ~FilterMatrix()
192 {
193 if ( m_entries )
194 std::free( m_entries );
195 }
196
197 inline int numColumns() const
198 {
199 return m_numColumns;
200 }
201
202 inline int numRows() const
203 {
204 return m_numRows;
205 }
206
207 inline void addSample( double x, double y,
208 double u, double v )
209 {
210 if ( x >= m_x0 && x < m_x1
211 && y >= m_y0 && y < m_y1 )
212 {
213 Entry& entry = m_entries[ indexOf( x, y ) ];
214 entry.addSample( x, y, u, v );
215 }
216 }
217
218 const FilterMatrix::Entry* entries() const
219 {
220 return m_entries;
221 }
222
223 private:
224 inline int indexOf( qreal x, qreal y ) const
225 {
226 const int col = ( x - m_x0 ) / m_dx;
227 const int row = ( y - m_y0 ) / m_dy;
228
229 return row * m_numColumns + col;
230 }
231
232 qreal m_x0, m_x1, m_y0, m_y1, m_dx, m_dy;
233 int m_numColumns;
234 int m_numRows;
235
236 Entry* m_entries;
237 };
238}
239
240class QwtPlotVectorField::PrivateData
241{
242 public:
243 PrivateData()
244 : pen( Qt::black )
245 , brush( Qt::black )
246 , indicatorOrigin( QwtPlotVectorField::OriginHead )
247 , magnitudeScaleFactor( 1.0 )
248 , rasterSize( 20, 20 )
249 , minArrowLength( 0.0 )
250 , maxArrowLength( std::numeric_limits< short >::max() )
251 , magnitudeModes( MagnitudeAsLength )
252 {
253 colorMap = NULL;
254 symbol = new QwtVectorFieldThinArrow();
255 }
256
257 ~PrivateData()
258 {
259 delete colorMap;
260 delete symbol;
261 }
262
263 QPen pen;
264 QBrush brush;
265
266 IndicatorOrigin indicatorOrigin;
267 QwtVectorFieldSymbol* symbol;
268 QwtColorMap* colorMap;
269
270 /*
271 Stores the range of magnitudes to be used for the color map.
272 If invalid (min=max or negative values), the range is determined
273 from the data samples themselves.
274 */
275 QwtInterval magnitudeRange;
276 QwtInterval boundingMagnitudeRange;
277
278 qreal magnitudeScaleFactor;
279 QSizeF rasterSize;
280
281 double minArrowLength;
282 double maxArrowLength;
283
284 PaintAttributes paintAttributes;
285 MagnitudeModes magnitudeModes;
286};
287
293 : QwtPlotSeriesItem( title )
294{
295 init();
296}
297
303 : QwtPlotSeriesItem( QwtText( title ) )
304{
305 init();
306}
307
310{
311 delete m_data;
312}
313
317void QwtPlotVectorField::init()
318{
321
322 m_data = new PrivateData;
324
325 setZ( 20.0 );
326}
327
336void QwtPlotVectorField::setPen( const QPen& pen )
337{
338 if ( m_data->pen != pen )
339 {
340 m_data->pen = pen;
341
342 itemChanged();
344 }
345}
346
352{
353 return m_data->pen;
354}
355
364void QwtPlotVectorField::setBrush( const QBrush& brush )
365{
366 if ( m_data->brush != brush )
367 {
368 m_data->brush = brush;
369
370 itemChanged();
372 }
373}
374
380{
381 return m_data->brush;
382}
383
391{
392 m_data->indicatorOrigin = origin;
393 if ( m_data->indicatorOrigin != origin )
394 {
395 m_data->indicatorOrigin = origin;
396 itemChanged();
397 }
398}
399
402{
403 return m_data->indicatorOrigin;
404}
405
418{
419 if ( factor != m_data->magnitudeScaleFactor )
420 {
421 m_data->magnitudeScaleFactor = factor;
422 itemChanged();
423 }
424}
425
441{
442 return m_data->magnitudeScaleFactor;
443}
444
450void QwtPlotVectorField::setRasterSize( const QSizeF& size )
451{
452 if ( size != m_data->rasterSize )
453 {
454 m_data->rasterSize = size;
455 itemChanged();
456 }
457}
458
464{
465 return m_data->rasterSize;
466}
467
476 PaintAttribute attribute, bool on )
477{
478 PaintAttributes attributes = m_data->paintAttributes;
479
480 if ( on )
481 attributes |= attribute;
482 else
483 attributes &= ~attribute;
484
485 if ( m_data->paintAttributes != attributes )
486 {
487 m_data->paintAttributes = attributes;
488 itemChanged();
489 }
490}
491
497 PaintAttribute attribute ) const
498{
499 return ( m_data->paintAttributes & attribute );
500}
501
507
517{
518 if ( m_data->symbol == symbol )
519 return;
520
521 delete m_data->symbol;
522 m_data->symbol = symbol;
523
524 itemChanged();
526}
527
533{
534 return m_data->symbol;
535}
536
542{
543 setData( new QwtVectorFieldData( samples ) );
544}
545
560
572{
573 if ( colorMap == NULL )
574 return;
575
576 if ( colorMap != m_data->colorMap )
577 {
578 delete m_data->colorMap;
579 m_data->colorMap = colorMap;
580 }
581
583 itemChanged();
584}
585
591{
592 return m_data->colorMap;
593}
594
603{
604 if ( on == testMagnitudeMode( mode ) )
605 return;
606
607 if ( on )
608 m_data->magnitudeModes |= mode;
609 else
610 m_data->magnitudeModes &= ~mode;
611
612 itemChanged();
613}
614
620{
621 return m_data->magnitudeModes & mode;
622}
623
633{
634 if ( m_data->magnitudeRange != magnitudeRange )
635 {
636 m_data->magnitudeRange = magnitudeRange;
637 itemChanged();
638 }
639}
640
646{
647 return m_data->magnitudeRange;
648}
649
659{
660 length = qMax( length, 0.0 );
661
662 if ( m_data->minArrowLength != length )
663 {
664 m_data->minArrowLength = length;
665 itemChanged();
666 }
667}
668
676{
677 return m_data->minArrowLength;
678}
679
689{
690 length = qMax( length, 0.0 );
691
692 if ( m_data->maxArrowLength != length )
693 {
694 m_data->maxArrowLength = length;
695 itemChanged();
696 }
697}
698
706{
707 return m_data->maxArrowLength;
708}
709
726double QwtPlotVectorField::arrowLength( double magnitude ) const
727{
728#if 0
729 /*
730 Normalize magnitude with respect to value range. Then, magnitudeScaleFactor
731 is the number of pixels to draw for a vector of length equal to
732 magnitudeRange.maxValue(). The relative scaling ensures that change of data
733 samples of very different magnitudes will always lead to a reasonable
734 display on screen.
735 */
736 const QwtVectorFieldData* vectorData = dynamic_cast< const QwtVectorFieldData* >( data() );
737 if ( m_data->magnitudeRange.maxValue() > 0 )
738 magnitude /= m_data->magnitudeRange.maxValue();
739#endif
740
741 double length = magnitude * m_data->magnitudeScaleFactor;
742
743 if ( length > 0.0 )
744 length = qBound( m_data->minArrowLength, length, m_data->maxArrowLength );
745
746 return length;
747}
748
750{
751#if 0
752 /*
753 The bounding rectangle of the samples comes from the origins
754 only, but as we know the scaling factor for the magnitude
755 ( qwtVector2Magnitude ) here, we could try to include it ?
756 */
757#endif
758
760}
761
771 int index, const QSizeF& size ) const
772{
773 Q_UNUSED( index );
774
775 QwtGraphic icon;
776 icon.setDefaultSize( size );
777
778 if ( size.isEmpty() )
779 return icon;
780
781 QPainter painter( &icon );
782 painter.setRenderHint( QPainter::Antialiasing,
784
785 painter.translate( -size.width(), -0.5 * size.height() );
786
787 painter.setPen( m_data->pen );
788 painter.setBrush( m_data->brush );
789
790 m_data->symbol->setLength( size.width() - 2 );
791 m_data->symbol->paint( &painter );
792
793 return icon;
794}
795
807void QwtPlotVectorField::drawSeries( QPainter* painter,
808 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
809 const QRectF& canvasRect, int from, int to ) const
810{
811 if ( !painter || dataSize() <= 0 )
812 return;
813
814 if ( to < 0 )
815 to = dataSize() - 1;
816
817 if ( from < 0 )
818 from = 0;
819
820 if ( from > to )
821 return;
822
823#if DEBUG_RENDER
824 QElapsedTimer timer;
825 timer.start();
826#endif
827
828 drawSymbols( painter, xMap, yMap, canvasRect, from, to );
829
830#if DEBUG_RENDER
831 qDebug() << timer.elapsed();
832#endif
833}
834
847void QwtPlotVectorField::drawSymbols( QPainter* painter,
848 const QwtScaleMap& xMap, const QwtScaleMap& yMap,
849 const QRectF& canvasRect, int from, int to ) const
850{
851 const bool doAlign = QwtPainter::roundingAlignment( painter );
852 const bool doClip = false;
853
854 const bool isInvertingX = xMap.isInverting();
855 const bool isInvertingY = yMap.isInverting();
856
858
859 if ( m_data->magnitudeModes & MagnitudeAsColor )
860 {
861 // user input error, can't draw without color map
862 // TODO: Discuss! Without colormap, silently fall back to uniform colors?
863 if ( m_data->colorMap == NULL)
864 return;
865 }
866 else
867 {
868 painter->setPen( m_data->pen );
869 painter->setBrush( m_data->brush );
870 }
871
872 if ( ( m_data->paintAttributes & FilterVectors ) && !m_data->rasterSize.isEmpty() )
873 {
874 const QRectF dataRect = QwtScaleMap::transform(
875 xMap, yMap, boundingRect() );
876
877 // TODO: Discuss. How to handle raster size when switching from screen to print size!
878 // DPI-aware adjustment of rastersize? Or make "rastersize in screen coordinate"
879 // or "rastersize in plotcoordinetes" a user option?
880#if 1
881 // define filter matrix based on screen/print coordinates
882 FilterMatrix matrix( dataRect, canvasRect, m_data->rasterSize );
883#else
884 // define filter matrix based on real coordinates
885
886 // get scale factor from real coordinates to screen coordinates
887 double xScale = 1;
888 if (xMap.sDist() != 0)
889 xScale = xMap.pDist() / xMap.sDist();
890
891 double yScale = 1;
892 if (yMap.sDist() != 0)
893 yScale = yMap.pDist() / yMap.sDist();
894
895 QSizeF canvasRasterSize(xScale * m_data->rasterSize.width(), yScale * m_data->rasterSize.height() );
896 FilterMatrix matrix( dataRect, canvasRect, canvasRasterSize );
897#endif
898
899 for ( int i = from; i <= to; i++ )
900 {
901 const QwtVectorFieldSample sample = series->sample( i );
902 if ( !sample.isNull() )
903 {
904 matrix.addSample( xMap.transform( sample.x ),
905 yMap.transform( sample.y ), sample.vx, sample.vy );
906 }
907 }
908
909 const int numEntries = matrix.numRows() * matrix.numColumns();
910 const FilterMatrix::Entry* entries = matrix.entries();
911
912 for ( int i = 0; i < numEntries; i++ )
913 {
914 const FilterMatrix::Entry& entry = entries[i];
915
916 if ( entry.count == 0 )
917 continue;
918
919 double xi = entry.x / entry.count;
920 double yi = entry.y / entry.count;
921
922 if ( doAlign )
923 {
924 xi = qRound( xi );
925 yi = qRound( yi );
926 }
927
928 const double vx = entry.vx / entry.count;
929 const double vy = entry.vy / entry.count;
930
931 drawSymbol( painter, xi, yi,
932 isInvertingX ? -vx : vx, isInvertingY ? -vy : vy );
933 }
934 }
935 else
936 {
937 for ( int i = from; i <= to; i++ )
938 {
939 const QwtVectorFieldSample sample = series->sample( i );
940
941 // arrows with zero length are never drawn
942 if ( sample.isNull() )
943 continue;
944
945 double xi = xMap.transform( sample.x );
946 double yi = yMap.transform( sample.y );
947
948 if ( doAlign )
949 {
950 xi = qRound( xi );
951 yi = qRound( yi );
952 }
953
954 if ( doClip )
955 {
956 if ( !canvasRect.contains( xi, yi ) )
957 continue;
958 }
959
960 drawSymbol( painter, xi, yi,
961 isInvertingX ? -sample.vx : sample.vx,
962 isInvertingY ? -sample.vy : sample.vy );
963 }
964 }
965}
966
975void QwtPlotVectorField::drawSymbol( QPainter* painter,
976 double x, double y, double vx, double vy ) const
977{
978 const double magnitude = qwtVector2Magnitude( vx, vy );
979
980 const QTransform oldTransform = painter->transform();
981
982 QTransform transform = qwtSymbolTransformation( oldTransform,
983 x, y, vx, vy, magnitude );
984
985 QwtVectorFieldSymbol* symbol = m_data->symbol;
986
987 double length = 0.0;
988
989 if ( m_data->magnitudeModes & MagnitudeAsLength )
990 {
991 length = arrowLength( magnitude );
992 }
993
994 symbol->setLength( length );
995
996 if( m_data->indicatorOrigin == OriginTail )
997 {
998 const qreal dx = symbol->length();
999 transform.translate( dx, 0.0 );
1000 }
1001 else if ( m_data->indicatorOrigin == OriginCenter )
1002 {
1003 const qreal dx = symbol->length();
1004 transform.translate( 0.5 * dx, 0.0 );
1005 }
1006
1007 if ( m_data->magnitudeModes & MagnitudeAsColor )
1008 {
1009 // Determine color for arrow if colored by magnitude.
1010
1011 QwtInterval range = m_data->magnitudeRange;
1012
1013 if ( !range.isValid() )
1014 {
1015 if ( !m_data->boundingMagnitudeRange.isValid() )
1016 m_data->boundingMagnitudeRange = qwtMagnitudeRange( data() );
1017
1018 range = m_data->boundingMagnitudeRange;
1019 }
1020
1021 const QColor c = m_data->colorMap->rgb( range, magnitude );
1022
1023#if 1
1024 painter->setBrush( c );
1025 painter->setPen( c );
1026#endif
1027 }
1028
1029 painter->setWorldTransform( transform, false );
1030 symbol->paint( painter );
1031 painter->setWorldTransform( oldTransform, false );
1032}
1033
1035{
1036 m_data->boundingMagnitudeRange.invalidate();
1038}
Template class for data, that is organized as QVector.
QwtColorMap is used to map values into colors.
virtual QRgb rgb(const QwtInterval &interval, double value) const =0
A paint device for scalable graphics.
Definition qwt_graphic.h:76
void setDefaultSize(const QSizeF &)
Set a default size.
A class representing an interval.
double maxValue() const
void invalidate()
bool isValid() const
static bool roundingAlignment()
virtual void legendChanged()
void setZ(double z)
Set the z value.
void setItemAttribute(ItemAttribute, bool on=true)
@ Rtti_PlotVectorField
For QwtPlotVectorField.
@ RenderAntialiased
Enable antialiasing.
bool testRenderHint(RenderHint) const
virtual void itemChanged()
@ Legend
The item is represented on the legend.
Base class for plot items representing a series of samples.
virtual void dataChanged() override
dataChanged() indicates, that the series has been changed.
virtual QRectF boundingRect() const override
A plot item, that represents a vector field.
virtual QwtGraphic legendIcon(int index, const QSizeF &) const override
@ OriginCenter
The arrow is centered at the sample position.
@ OriginHead
symbol points to the sample position
@ OriginTail
The arrow starts at the sample position.
virtual int rtti() const override
const QwtColorMap * colorMap() const
double magnitudeScaleFactor() const
void setSamples(const QVector< QwtVectorFieldSample > &)
void setRasterSize(const QSizeF &)
virtual void drawSeries(QPainter *, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to) const override
void setBrush(const QBrush &)
Assign a brush.
void setSymbol(QwtVectorFieldSymbol *)
bool testPaintAttribute(PaintAttribute) const
virtual void drawSymbol(QPainter *, double x, double y, double vx, double vy) const
virtual QRectF boundingRect() const override
bool testMagnitudeMode(MagnitudeMode) const
void setMagnitudeScaleFactor(double factor)
Set the magnitudeScaleFactor.
const QwtVectorFieldSymbol * symbol() const
virtual void drawSymbols(QPainter *, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to) const
void setColorMap(QwtColorMap *)
QwtPlotVectorField(const QString &title=QString())
void setMagnitudeRange(const QwtInterval &)
void setPen(const QPen &)
QFlags< MagnitudeMode > MagnitudeModes
virtual ~QwtPlotVectorField()
Destructor.
QFlags< PaintAttribute > PaintAttributes
void setPaintAttribute(PaintAttribute, bool on=true)
virtual void dataChanged() override
dataChanged() indicates, that the series has been changed.
QwtInterval magnitudeRange() const
void setIndicatorOrigin(IndicatorOrigin)
virtual double arrowLength(double magnitude) const
IndicatorOrigin indicatorOrigin() const
void setMagnitudeMode(MagnitudeMode, bool on=true)
A scale map.
bool isInverting() const
double pDist() const
double transform(double s) const
double sDist() const
virtual size_t size() const =0
virtual T sample(size_t i) const =0
QwtVectorFieldSample sample(int index) const
virtual size_t dataSize() const override
QwtSeriesData< QwtVectorFieldSample > * data()
void setData(QwtSeriesData< QwtVectorFieldSample > *series)
virtual QRectF dataRect() const override
A class representing a text.
Definition qwt_text.h:52
Sample used in vector fields.
double y
y coordinate of the position
bool isNull() const
double vx
x coordinate of the vector
double x
x coordinate of the position
double vy
y coordinate of the vector
virtual qreal length() const =0
virtual void paint(QPainter *) const =0
Draw the symbol/arrow.
virtual void setLength(qreal length)=0