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 )
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 ) );
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 )
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) );
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 
260  if ( LineDiagram::Private* lineDiagram = dynamic_cast<LineDiagram::Private*>( diagramPrivate ) ) {
261  tension = lineDiagram->tension;
262  }
263 
264  if ( qFuzzyIsNull(tension) ) {
265  paintPolyline( ctx, brush, pen, points );
266  } else {
267  paintSpline( ctx, brush, pen, points, tension );
268  }
269 }
270 
271 void paintElements( AbstractDiagram::Private *diagramPrivate, PaintContext* ctx,
272  const LabelPaintCache& lpc, const LineAttributesInfoList& lineList )
273 {
274  AbstractDiagram* diagram = diagramPrivate->diagram;
275  // paint all lines and their attributes
276  const PainterSaver painterSaver( ctx->painter() );
277  ctx->painter()->setRenderHint( QPainter::Antialiasing, diagram->antiAliasing() );
278 
279  QBrush curBrush;
280  QPen curPen;
281  QPolygonF points;
282  KDAB_FOREACH ( const LineAttributesInfo& lineInfo, lineList ) {
283  const QModelIndex& index = lineInfo.index;
284  const ThreeDLineAttributes td = threeDLineAttributes( diagram, index );
285 
286  if ( td.isEnabled() ) {
287  PaintingHelpers::paintThreeDLines( ctx, diagram, index, lineInfo.value,
288  lineInfo.nextValue, td, &diagramPrivate->reverseMapper );
289  } else {
290  const QBrush brush( diagram->brush( index ) );
291  const QPen pen( diagram->pen( index ) );
292 
293  // line goes from lineInfo.value to lineInfo.nextValue
294  // We don't want it added if we're not drawing it, since the reverse mapper is used
295  // for lookup when trying to find e.g. tooltips. Having the line added when invisible gives
296  // us tooltips in empty areas.
297  if (pen.style() != Qt::NoPen)
298  diagramPrivate->reverseMapper.addLine( lineInfo.index.row(), lineInfo.index.column(),
299  lineInfo.value, lineInfo.nextValue );
300 
301  if ( points.count() && points.last() == lineInfo.value && curBrush == brush && curPen == pen ) {
302  // continue the current run of lines
303  } else {
304  // different painter settings or discontinuous line: start a new run of lines
305  if ( points.count() ) {
306  paintObject( diagramPrivate, ctx, curBrush, curPen, points );
307  }
308  curBrush = brush;
309  curPen = pen;
310  points.clear();
311  points << lineInfo.value;
312  }
313  points << lineInfo.nextValue;
314  }
315  }
316  if ( points.count() ) {
317  // the last run of lines is yet to be painted - do it now
318  paintObject( diagramPrivate, ctx, curBrush, curPen, points );
319  }
320 
321  KDAB_FOREACH ( const LineAttributesInfo& lineInfo, lineList ) {
322  const ValueTrackerAttributes vt = valueTrackerAttributes( diagram, lineInfo.index );
323  if ( vt.isEnabled() ) {
324  PaintingHelpers::paintValueTracker( ctx, vt, lineInfo.nextValue );
325  }
326  }
327 
328  // paint all data value texts and the point markers
329  diagramPrivate->paintDataValueTextsAndMarkers( ctx, lpc, true );
330 }
331 
332 void paintAreas( AbstractDiagram::Private* diagramPrivate, PaintContext* ctx, const QModelIndex& index,
333  const QList< QPolygonF >& areas, uint opacity )
334 {
335  AbstractDiagram* diagram = diagramPrivate->diagram;
336  QPainterPath path;
337  for ( int i = 0; i < areas.count(); ++i )
338  {
339  const QPolygonF& p = areas[ i ];
340  path.addPolygon( p );
341  diagramPrivate->reverseMapper.addPolygon( index.row(), index.column(), p );
342  path.closeSubpath();
343  }
344 
345  ThreeDLineAttributes threeDAttrs = threeDLineAttributes( diagram, index );
346  QBrush trans = diagram->brush( index );
347  if ( threeDAttrs.isEnabled() ) {
348  trans = threeDAttrs.threeDBrush( trans, path.boundingRect() );
349  }
350  QColor transColor = trans.color();
351  transColor.setAlpha( opacity );
352  trans.setColor(transColor);
353  QPen indexPen = diagram->pen(index);
354  indexPen.setBrush( trans );
355  const PainterSaver painterSaver( ctx->painter() );
356 
357  ctx->painter()->setRenderHint( QPainter::Antialiasing, diagram->antiAliasing() );
358  ctx->painter()->setPen( PrintingParameters::scalePen( indexPen ) );
359  ctx->painter()->setBrush( trans );
360 
361  ctx->painter()->drawPath( path );
362 }
363 
364 void paintAreas( AbstractDiagram::Private* diagramPrivate, PaintContext* ctx, const QModelIndex& index,
365  const QList< QPainterPath >& areas, uint opacity )
366 {
367  AbstractDiagram* diagram = diagramPrivate->diagram;
368  QPainterPath path;
369  for ( int i = 0; i < areas.count(); ++i )
370  {
371  path += areas[ i ];
372  // diagramPrivate->reverseMapper.addPolygon( index.row(), index.column(), p );
373  }
374 
375  QBrush trans = diagram->brush( index );
376  QColor transColor = trans.color();
377  transColor.setAlpha( opacity );
378  trans.setColor( transColor );
379  QPen indexPen = diagram->pen( index );
380  indexPen.setBrush( trans );
381  const PainterSaver painterSaver( ctx->painter() );
382 
383  ctx->painter()->setRenderHint( QPainter::Antialiasing, diagram->antiAliasing() );
384  ctx->painter()->setPen( PrintingParameters::scalePen( indexPen ) );
385  ctx->painter()->setBrush( trans );
386 
387  ctx->painter()->drawPath( path );
388 }
389 
390 } // namespace PaintingHelpers
391 } // 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)
QPainterPath fitPoints(const QPolygonF &points, qreal tension)
LineDiagram defines a common line diagram.
void paintSpline(PaintContext *ctx, const QBrush &brush, const QPen &pen, const QPolygonF &points, qreal tension)
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)
static ThreeDLineAttributes threeDLineAttributes(AbstractDiagram *diagram, const QModelIndex &index)
A set of 3D line attributes.

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/