KD Chart 2  [rev.2.7]
PaintingHelpers_p.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 ** Copyright (C) 2001-2020 Klaralvdalens Datakonsult AB. All rights reserved.
3 **
4 ** This file is part of the KD Chart library.
5 **
6 ** Licensees holding valid commercial KD Chart licenses may use this file in
7 ** accordance with the KD Chart Commercial License Agreement provided with
8 ** the Software.
9 **
10 **
11 ** This file may be distributed and/or modified under the terms of the
12 ** GNU General Public License version 2 and version 3 as published by the
13 ** Free Software Foundation and appearing in the file LICENSE.GPL.txt included.
14 **
15 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
16 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
17 **
18 ** Contact info@kdab.com if any conditions of this licensing are not
19 ** clear to you.
20 **
21 **********************************************************************/
22 
23 #include "PaintingHelpers_p.h"
24 
25 #include "KDChartGlobal.h"
26 
27 #include "KDChartAbstractDiagram.h"
28 #include "KDChartAbstractDiagram_p.h"
30 #include "KDChartLineDiagram.h"
31 #include "KDChartLineDiagram_p.h"
33 #include "KDChartPaintContext.h"
34 #include "KDChartPainterSaver_p.h"
35 #include "KDChartPlotter.h"
38 #include "ReverseMapper.h"
39 
40 namespace KDChart {
41 namespace PaintingHelpers {
42 
48 const QPointF project( const QPointF& point, const ThreeDLineAttributes& tdAttributes )
49 {
50  //Pending Michel FIXME - the rotation does not work as expected atm
51  qreal xrad = DEGTORAD( tdAttributes.lineXRotation() );
52  qreal yrad = DEGTORAD( tdAttributes.lineYRotation() );
53  return QPointF( point.x() * cos( yrad ) + tdAttributes.depth() * sin( yrad ),
54  point.y() * cos( xrad ) - tdAttributes.depth() * sin( xrad ) );
55 }
56 
57 QPainterPath fitPoints( const QPolygonF &points, qreal tension, SplineDirection splineDirection )
58 {
59  QPainterPath path;
60  path.moveTo( points.at( 0 ) );
61  const int count = points.size();
62 
63  // TODO: convert to lambda when we stop caring about pre-C++11
64  // auto dataAt = [&] (int i) {
65  // return i < 0 || i >= count ? QPointF(NAN, NAN) : points.at( i );
66  // };
67  struct dataAtLambda {
68  dataAtLambda(const QPolygonF &points, int count)
69  : points(points)
70  , count(count)
71  {}
72 
73  const QPolygonF &points;
74  int count;
75 
76  QPointF operator() (int i) const
77  {
78  return i < 0 || i >= count ? QPointF(NAN, NAN) : points.at( i );
79  }
80  };
81 
82  dataAtLambda dataAt(points, count);
83 
84  for (int i = 1; i < count; ++i) {
85  addSplineChunkTo( path, tension, dataAt( i - 2 ), points.at( i - 1 ), points.at( i ), dataAt( i + 1 ), splineDirection );
86  }
87 
88  return path;
89 }
90 
91 void paintPolyline( PaintContext* ctx, const QBrush& brush, const QPen& pen, const QPolygonF& points )
92 {
93  ctx->painter()->setBrush( brush );
94  ctx->painter()->setPen( PrintingParameters::scalePen(
95  QPen( pen.color(), pen.width(), pen.style(), Qt::FlatCap, Qt::MiterJoin ) ) );
96 #if QT_VERSION > 0x040299
97  ctx->painter()->drawPolyline( points );
98 #else
99  // FIXME (Mirko) verify, this sounds reverse-logical
100  // For Qt versions older than 4.3 drawPolyline is VERY slow
101  // so we use traditional line segments drawing instead then.
102  for ( int i = 0; i < points.size()-1; ++i ) {
103  ctx->painter()->drawLine( points.at( i ), points.at( i + 1 ) );
104  }
105 #endif
106 }
107 
108 void paintSpline( PaintContext* ctx, const QBrush& brush, const QPen& pen, const QPolygonF& points, qreal tension, SplineDirection splineDirection )
109 {
110  if (points.size() < 3) {
111  paintPolyline( ctx, brush, pen, points );
112  return;
113  }
114 
115  ctx->painter()->setBrush( brush );
116  ctx->painter()->setBrush( QBrush() );
117  ctx->painter()->setPen( PrintingParameters::scalePen(
118  QPen( pen.color(), pen.width(), pen.style(), Qt::FlatCap, Qt::MiterJoin ) ) );
119 
120  ctx->painter()->drawPath( fitPoints(points, tension, splineDirection) );
121 }
122 
123 void paintThreeDLines( PaintContext* ctx, AbstractDiagram *diagram, const QModelIndex& index,
124  const QPointF& from, const QPointF& to, const ThreeDLineAttributes& tdAttributes,
125  ReverseMapper* reverseMapper )
126 {
127  const QPointF topLeft = project( from, tdAttributes );
128  const QPointF topRight = project ( to, tdAttributes );
129  const QPolygonF segment = QPolygonF() << from << topLeft << topRight << to;
130 
131  QBrush indexBrush( diagram->brush( index ) );
132  indexBrush = tdAttributes.threeDBrush( indexBrush, QRectF(topLeft, topRight) );
133 
134  const PainterSaver painterSaver( ctx->painter() );
135 
136  ctx->painter()->setRenderHint( QPainter::Antialiasing, diagram->antiAliasing() );
137  ctx->painter()->setBrush( indexBrush );
138  ctx->painter()->setPen( PrintingParameters::scalePen( diagram->pen( index ) ) );
139 
140  reverseMapper->addPolygon( index.row(), index.column(), segment );
141  ctx->painter()->drawPolygon( segment );
142 }
143 
144 void paintValueTracker( PaintContext* ctx, const ValueTrackerAttributes& vt, const QPointF& at )
145 {
146  CartesianCoordinatePlane* plane = qobject_cast<CartesianCoordinatePlane*>( ctx->coordinatePlane() );
147  if ( !plane )
148  return;
149 
150  DataDimensionsList gridDimensions = ctx->coordinatePlane()->gridDimensionsList();
151  const QPointF bottomLeft( ctx->coordinatePlane()->translate(
152  QPointF( plane->isHorizontalRangeReversed() ?
153  gridDimensions.at( 0 ).end :
154  gridDimensions.at( 0 ).start,
155  plane->isVerticalRangeReversed() ?
156  gridDimensions.at( 1 ).end :
157  gridDimensions.at( 1 ).start ) ) );
158  const QPointF topRight( ctx->coordinatePlane()->translate(
159  QPointF( plane->isHorizontalRangeReversed() ?
160  gridDimensions.at( 0 ).start :
161  gridDimensions.at( 0 ).end,
162  plane->isVerticalRangeReversed() ?
163  gridDimensions.at( 1 ).start :
164  gridDimensions.at( 1 ).end ) ) );
165  const QPointF markerPoint = at;
166 
167  QPointF startPoint;
168  if ( vt.orientations() & Qt::Horizontal ) {
169  startPoint = QPointF( bottomLeft.x(), at.y() );
170  } else {
171  startPoint = QPointF( at.x(), topRight.y() );
172  }
173 
174  QPointF endPoint;
175  if ( vt.orientations() & Qt::Vertical ) {
176  endPoint = QPointF( at.x(), bottomLeft.y() );
177  } else {
178  endPoint = QPointF( topRight.x(), at.y() );
179  }
180 
181  const QSizeF markerSize = vt.markerSize();
182  const QRectF ellipseMarker = QRectF( at.x() - markerSize.width() / 2,
183  at.y() - markerSize.height() / 2,
184  markerSize.width(), markerSize.height() );
185 
186  QPointF startMarker[3];
187  if ( vt.orientations() & Qt::Horizontal ) {
188  startMarker[0] = startPoint + QPointF( 0, markerSize.height() / 2 );
189  startMarker[1] = startPoint + QPointF( markerSize.width() / 2, 0 );
190  startMarker[2] = startPoint - QPointF( 0, markerSize.height() / 2 );
191  } else {
192  startMarker[0] = startPoint + QPointF( 0, markerSize.height() / 2 );
193  startMarker[1] = startPoint + QPointF( markerSize.width() / 2, 0 );
194  startMarker[2] = startPoint - QPointF( markerSize.width() / 2, 0 );
195  }
196 
197  QPointF endMarker[3];
198 
199  if ( vt.orientations() & Qt::Vertical ) {
200  endMarker[0] = endPoint + QPointF( markerSize.width() / 2, 0 );
201  endMarker[1] = endPoint - QPointF( 0, markerSize.height() / 2 );
202  endMarker[2] = endPoint - QPointF( markerSize.width() / 2, 0 );
203  } else {
204  endMarker[0] = endPoint + QPointF( 0, markerSize.width() / 2 );
205  endMarker[1] = endPoint - QPointF( 0, markerSize.height() / 2 );
206  endMarker[2] = endPoint - QPointF( markerSize.width() / 2, 0 );
207  }
208 
209  QPointF topLeft = startPoint;
210  QPointF bottomRightOffset = endPoint - topLeft;
211  QSizeF size( bottomRightOffset.x(), bottomRightOffset.y() );
212  QRectF area( topLeft, size );
213 
214  PainterSaver painterSaver( ctx->painter() );
215  ctx->painter()->setPen( PrintingParameters::scalePen( vt.linePen() ) );
216  ctx->painter()->setBrush( QBrush() );
217  ctx->painter()->drawLine( markerPoint, startPoint );
218  ctx->painter()->drawLine( markerPoint, endPoint );
219 
220  ctx->painter()->fillRect( area, vt.areaBrush() );
221 
222  ctx->painter()->setPen( PrintingParameters::scalePen( vt.markerPen() ) );
223  ctx->painter()->setBrush( vt.markerBrush() );
224  ctx->painter()->drawEllipse( ellipseMarker );
225 
226  ctx->painter()->setPen( PrintingParameters::scalePen( vt.arrowBrush().color() ) );
227  ctx->painter()->setBrush( vt.arrowBrush() );
228  ctx->painter()->drawPolygon( startMarker, 3 );
229  ctx->painter()->drawPolygon( endMarker, 3 );
230 }
231 
232 // ### for BC reasons we cannot insert a common interface for LineDiagram and Plotter into the class
233 // hierarchy, so we have to use hacks to use their common methods
234 static ThreeDLineAttributes threeDLineAttributes( AbstractDiagram* diagram, const QModelIndex& index )
235 {
236  if ( Plotter *plotter = qobject_cast< Plotter* >( diagram ) ) {
237  return plotter->threeDLineAttributes( index );
238  } else if ( LineDiagram *lineDiagram = qobject_cast< LineDiagram* >( diagram ) ) {
239  return lineDiagram->threeDLineAttributes( index );
240  }
241  Q_ASSERT( false );
242  return ThreeDLineAttributes();
243 }
244 
245 static ValueTrackerAttributes valueTrackerAttributes( AbstractDiagram* diagram, const QModelIndex& index )
246 {
247  if ( Plotter *plotter = qobject_cast< Plotter* >( diagram ) ) {
248  return plotter->valueTrackerAttributes( index );
249  } else if ( LineDiagram *lineDiagram = qobject_cast< LineDiagram* >( diagram ) ) {
250  return lineDiagram->valueTrackerAttributes( index );
251  }
252  Q_ASSERT( false );
253  return ValueTrackerAttributes();
254 }
255 
256 void paintObject ( AbstractDiagram::Private *diagramPrivate, PaintContext* ctx, const QBrush& brush, const QPen& pen, const QPolygonF& points )
257 {
258  qreal tension = 0;
259  SplineDirection splineDirection = NormalSplineDirection;
260 
261  if ( LineDiagram::Private* lineDiagram = dynamic_cast<LineDiagram::Private*>( diagramPrivate ) ) {
262  tension = lineDiagram->tension;
263  Q_ASSERT(dynamic_cast<CartesianCoordinatePlane*>(ctx->coordinatePlane()));
264  const auto plane = static_cast<CartesianCoordinatePlane*>(ctx->coordinatePlane());
265  splineDirection = plane->isHorizontalRangeReversed() ? ReverseSplineDirection : NormalSplineDirection;
266  }
267 
268  if ( qFuzzyIsNull(tension) ) {
269  paintPolyline( ctx, brush, pen, points );
270  } else {
271  paintSpline( ctx, brush, pen, points, tension, splineDirection );
272  }
273 }
274 
275 void paintElements( AbstractDiagram::Private *diagramPrivate, PaintContext* ctx,
276  const LabelPaintCache& lpc, const LineAttributesInfoList& lineList )
277 {
278  AbstractDiagram* diagram = diagramPrivate->diagram;
279  // paint all lines and their attributes
280  const PainterSaver painterSaver( ctx->painter() );
281  ctx->painter()->setRenderHint( QPainter::Antialiasing, diagram->antiAliasing() );
282 
283  QBrush curBrush;
284  QPen curPen;
285  QPolygonF points;
286  KDAB_FOREACH ( const LineAttributesInfo& lineInfo, lineList ) {
287  const QModelIndex& index = lineInfo.index;
288  const ThreeDLineAttributes td = threeDLineAttributes( diagram, index );
289 
290  if ( td.isEnabled() ) {
291  PaintingHelpers::paintThreeDLines( ctx, diagram, index, lineInfo.value,
292  lineInfo.nextValue, td, &diagramPrivate->reverseMapper );
293  } else {
294  const QBrush brush( diagram->brush( index ) );
295  const QPen pen( diagram->pen( index ) );
296 
297  // line goes from lineInfo.value to lineInfo.nextValue
298  // We don't want it added if we're not drawing it, since the reverse mapper is used
299  // for lookup when trying to find e.g. tooltips. Having the line added when invisible gives
300  // us tooltips in empty areas.
301  if (pen.style() != Qt::NoPen)
302  diagramPrivate->reverseMapper.addLine( lineInfo.index.row(), lineInfo.index.column(),
303  lineInfo.value, lineInfo.nextValue );
304 
305  if ( points.count() && points.last() == lineInfo.value && curBrush == brush && curPen == pen ) {
306  // continue the current run of lines
307  } else {
308  // different painter settings or discontinuous line: start a new run of lines
309  if ( points.count() ) {
310  paintObject( diagramPrivate, ctx, curBrush, curPen, points );
311  }
312  curBrush = brush;
313  curPen = pen;
314  points.clear();
315  points << lineInfo.value;
316  }
317  points << lineInfo.nextValue;
318  }
319  }
320  if ( points.count() ) {
321  // the last run of lines is yet to be painted - do it now
322  paintObject( diagramPrivate, ctx, curBrush, curPen, points );
323  }
324 
325  KDAB_FOREACH ( const LineAttributesInfo& lineInfo, lineList ) {
326  const ValueTrackerAttributes vt = valueTrackerAttributes( diagram, lineInfo.index );
327  if ( vt.isEnabled() ) {
328  PaintingHelpers::paintValueTracker( ctx, vt, lineInfo.nextValue );
329  }
330  }
331 
332  // paint all data value texts and the point markers
333  diagramPrivate->paintDataValueTextsAndMarkers( ctx, lpc, true );
334 }
335 
336 void paintAreas( AbstractDiagram::Private* diagramPrivate, PaintContext* ctx, const QModelIndex& index,
337  const QList< QPolygonF >& areas, uint opacity )
338 {
339  AbstractDiagram* diagram = diagramPrivate->diagram;
340  QPainterPath path;
341  for ( int i = 0; i < areas.count(); ++i )
342  {
343  const QPolygonF& p = areas[ i ];
344  path.addPolygon( p );
345  diagramPrivate->reverseMapper.addPolygon( index.row(), index.column(), p );
346  path.closeSubpath();
347  }
348 
349  ThreeDLineAttributes threeDAttrs = threeDLineAttributes( diagram, index );
350  QBrush trans = diagram->brush( index );
351  if ( threeDAttrs.isEnabled() ) {
352  trans = threeDAttrs.threeDBrush( trans, path.boundingRect() );
353  }
354  QColor transColor = trans.color();
355  transColor.setAlpha( opacity );
356  trans.setColor(transColor);
357  QPen indexPen = diagram->pen(index);
358  indexPen.setBrush( trans );
359  const PainterSaver painterSaver( ctx->painter() );
360 
361  ctx->painter()->setRenderHint( QPainter::Antialiasing, diagram->antiAliasing() );
362  ctx->painter()->setPen( PrintingParameters::scalePen( indexPen ) );
363  ctx->painter()->setBrush( trans );
364 
365  ctx->painter()->drawPath( path );
366 }
367 
368 void paintAreas( AbstractDiagram::Private* diagramPrivate, PaintContext* ctx, const QModelIndex& index,
369  const QList< QPainterPath >& areas, uint opacity )
370 {
371  AbstractDiagram* diagram = diagramPrivate->diagram;
372  QPainterPath path;
373  for ( int i = 0; i < areas.count(); ++i )
374  {
375  path += areas[ i ];
376  // diagramPrivate->reverseMapper.addPolygon( index.row(), index.column(), p );
377  }
378 
379  QBrush trans = diagram->brush( index );
380  QColor transColor = trans.color();
381  transColor.setAlpha( opacity );
382  trans.setColor( transColor );
383  QPen indexPen = diagram->pen( index );
384  indexPen.setBrush( trans );
385  const PainterSaver painterSaver( ctx->painter() );
386 
387  ctx->painter()->setRenderHint( QPainter::Antialiasing, diagram->antiAliasing() );
388  ctx->painter()->setPen( PrintingParameters::scalePen( indexPen ) );
389  ctx->painter()->setBrush( trans );
390 
391  ctx->painter()->drawPath( path );
392 }
393 
394 } // namespace PaintingHelpers
395 } // namespace KDChart
void paintElements(AbstractDiagram::Private *diagramPrivate, PaintContext *ctx, const LabelPaintCache &lpc, const LineAttributesInfoList &lineList)
const QPointF project(const QPointF &point, const ThreeDLineAttributes &tdAttributes)
virtual const QPointF translate(const QPointF &diagramPoint) const =0
Translate the given point in value space coordinates to a position in pixel space.
AbstractCoordinatePlane * coordinatePlane() const
static ValueTrackerAttributes valueTrackerAttributes(AbstractDiagram *diagram, const QModelIndex &index)
QPainter * painter() const
Plotter defines a diagram type plotting two-dimensional data.
AbstractDiagram defines the interface for diagram classes.
QBrush brush() const
Retrieve the brush to be used for painting datapoints globally.
The ReverseMapper stores information about objects on a chart and their respective model indexes...
Definition: ReverseMapper.h:44
void paintAreas(AbstractDiagram::Private *diagramPrivate, PaintContext *ctx, const QModelIndex &index, const QList< QPolygonF > &areas, uint opacity)
LineDiagram defines a common line diagram.
Cell-specific attributes regarding value tracking.
Stores information about painting diagrams.
void paintObject(AbstractDiagram::Private *diagramPrivate, PaintContext *ctx, const QBrush &brush, const QPen &pen, const QPolygonF &points)
QPen pen() const
Retrieve the pen to be used for painting datapoints globally.
virtual QBrush threeDBrush(const QBrush &brush, const QRectF &rect) const
DataDimensionsList gridDimensionsList()
Returns the dimensions used for drawing the grid lines.
void addPolygon(int row, int column, const QPolygonF &polygon)
void paintPolyline(PaintContext *ctx, const QBrush &brush, const QPen &pen, const QPolygonF &points)
static QPen scalePen(const QPen &pen)
void paintValueTracker(PaintContext *ctx, const ValueTrackerAttributes &vt, const QPointF &at)
void paintThreeDLines(PaintContext *ctx, AbstractDiagram *diagram, const QModelIndex &index, const QPointF &from, const QPointF &to, const ThreeDLineAttributes &tdAttributes, ReverseMapper *reverseMapper)
void paintSpline(PaintContext *ctx, const QBrush &brush, const QPen &pen, const QPolygonF &points, qreal tension, SplineDirection splineDirection)
static ThreeDLineAttributes threeDLineAttributes(AbstractDiagram *diagram, const QModelIndex &index)
A set of 3D line attributes.
QPainterPath fitPoints(const QPolygonF &points, qreal tension, SplineDirection splineDirection)

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/