KD Chart 2  [rev.2.7]
KDChartStockDiagram_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 "KDChartStockDiagram_p.h"
24 
25 #include "KDChartPainterSaver_p.h"
26 
27 using namespace KDChart;
28 
29 
30 class StockDiagram::Private::ThreeDPainter
31 {
32 public:
33  struct ThreeDProperties {
34  qreal depth;
35  qreal angle;
36  bool useShadowColors;
37  };
38 
39  ThreeDPainter( QPainter *p )
40  : painter( p ) {};
41 
42  QPolygonF drawTwoDLine( const QLineF &line, const QPen &pen,
43  const ThreeDProperties &props );
44  QPolygonF drawThreeDLine( const QLineF &line, const QBrush &brush,
45  const QPen &pen, const ThreeDProperties &props );
46  QPolygonF drawThreeDRect( const QRectF &rect, const QBrush &brush,
47  const QPen &pen, const ThreeDProperties &props );
48 
49 private:
50  QPointF projectPoint( const QPointF &point, qreal depth, qreal angle ) const;
51  QColor calcShadowColor( const QColor &color, qreal angle ) const;
52 
53  QPainter *painter;
54 };
55 
62 QPointF StockDiagram::Private::ThreeDPainter::projectPoint( const QPointF &point, qreal depth, qreal angle ) const
63 {
64  const qreal angleInRad = DEGTORAD( angle );
65  const qreal distX = depth * cos( angleInRad );
66  // Y coordinates are reversed on our coordinate plane
67  const qreal distY = depth * -sin( angleInRad );
68 
69  return QPointF( point.x() + distX, point.y() + distY );
70 }
71 
78 QColor StockDiagram::Private::ThreeDPainter::calcShadowColor( const QColor &color, qreal angle ) const
79 {
80  // The shadow factor determines to how many percent the brightness
81  // of the color can be reduced. That is, the darkest shadow color
82  // is color * shadowFactor.
83  const qreal shadowFactor = 0.5;
84  const qreal sinAngle = 1.0 - qAbs( sin( DEGTORAD( angle ) ) ) * shadowFactor;
85  return QColor( qRound( color.red() * sinAngle ),
86  qRound( color.green() * sinAngle ),
87  qRound( color.blue() * sinAngle ) );
88 }
89 
98 QPolygonF StockDiagram::Private::ThreeDPainter::drawTwoDLine( const QLineF &line, const QPen &pen,
99  const ThreeDProperties &props )
100 {
101  // Restores the painting properties when destroyed
102  PainterSaver painterSaver( painter );
103 
104  // The z coordinate to use (i.e., at what depth to draw the line)
105  const qreal z = props.depth / 2.0;
106 
107  // Projec the 2D points of the line in 3D
108  const QPointF deepP1 = projectPoint( line.p1(), z, props.angle );
109  const QPointF deepP2 = projectPoint( line.p2(), z, props.angle );
110 
111  // The drawn line with a width of 2px
112  QPolygonF threeDArea;
113  // The offset of the line "borders" from the center to each side
114  const QPointF offset( 0.0, 1.0 );
115  threeDArea << deepP1 - offset << deepP2 - offset
116  << deepP1 + offset << deepP2 + offset << deepP1 - offset;
117 
118  painter->setPen( pen );
119  painter->drawLine( QLineF( deepP1, deepP2 ) );
120 
121  return threeDArea;
122 }
123 
133 QPolygonF StockDiagram::Private::ThreeDPainter::drawThreeDLine( const QLineF &line, const QBrush &brush,
134  const QPen &pen, const ThreeDProperties &props )
135 {
136  // Restores the painting properties when destroyed
137  PainterSaver painterSaver( painter );
138 
139  const QPointF p1 = line.p1();
140  const QPointF p2 = line.p2();
141 
142  // Project the 2D points of the line in 3D
143  const QPointF deepP1 = projectPoint( p1, props.depth, props.angle );
144  const QPointF deepP2 = projectPoint( p2, props.depth, props.angle );
145 
146  // The result is a 3D representation of the 2D line
147  QPolygonF threeDArea;
148  threeDArea << p1 << p2 << deepP2 << deepP1 << p1;
149 
150  // Use shadow colors if ThreeDProperties::useShadowColors is set
151  // Note: Setting a new color on a brush or pen does not effect gradients or textures
152  if ( props.useShadowColors ) {
153  QBrush shadowBrush( brush );
154  QPen shadowPen( pen );
155  shadowBrush.setColor( calcShadowColor( brush.color(), props.angle ) );
156  shadowPen.setColor( calcShadowColor( pen.color(), props.angle ) );
157  painter->setBrush( shadowBrush );
158  painter->setPen( shadowPen );
159  } else {
160  painter->setBrush( brush );
161  painter->setPen( pen );
162  }
163 
164  painter->drawPolygon( threeDArea );
165 
166  return threeDArea;
167 }
168 
178 QPolygonF StockDiagram::Private::ThreeDPainter::drawThreeDRect( const QRectF &rect, const QBrush &brush,
179  const QPen &pen, const ThreeDProperties &props )
180 {
181  // Restores the painting properties when destroyed
182  PainterSaver painterSaver( painter );
183 
184  // Make sure that the top really is the top
185  const QRectF normalizedRect = rect.normalized();
186 
187  // Calculate all the four sides of the rectangle
188  const QLineF topSide = QLineF( normalizedRect.topLeft(), normalizedRect.topRight() );
189  const QLineF bottomSide = QLineF( normalizedRect.bottomLeft(), normalizedRect.bottomRight() );
190  const QLineF leftSide = QLineF( normalizedRect.topLeft(), normalizedRect.bottomLeft() );
191  const QLineF rightSide = QLineF( normalizedRect.topRight(), normalizedRect.bottomRight() );
192 
193  QPolygonF drawnPolygon;
194 
195  // Shorter names are easier on the eyes
196  const qreal angle = props.angle;
197 
198  // Only top and right side is visible
199  if ( angle >= 0.0 && angle < 90.0 ) {
200  drawnPolygon = drawnPolygon.united( drawThreeDLine( topSide, brush, pen, props ) );
201  drawnPolygon = drawnPolygon.united( drawThreeDLine( rightSide, brush, pen, props ) );
202  // Only top and left side is visible
203  } else if ( angle >= 90.0 && angle < 180.0 ) {
204  drawnPolygon = drawnPolygon.united( drawThreeDLine( topSide, brush, pen, props ) );
205  drawnPolygon = drawnPolygon.united( drawThreeDLine( leftSide, brush, pen, props ) );
206  // Only bottom and left side is visible
207  } else if ( angle >= 180.0 && angle < 270.0 ) {
208  drawnPolygon = drawnPolygon.united( drawThreeDLine( bottomSide, brush, pen, props ) );
209  drawnPolygon = drawnPolygon.united( drawThreeDLine( leftSide, brush, pen, props ) );
210  // Only bottom and right side is visible
211  } else if ( angle >= 270.0 && angle <= 360.0 ) {
212  drawnPolygon = drawnPolygon.united( drawThreeDLine( bottomSide, brush, pen, props ) );
213  drawnPolygon = drawnPolygon.united( drawThreeDLine( rightSide, brush, pen, props ) );
214  }
215 
216  // Draw the front side
217  painter->setPen( pen );
218  painter->setBrush( brush );
219  painter->drawRect( normalizedRect );
220 
221  return drawnPolygon;
222 }
223 
224 
225 StockDiagram::Private::Private()
226  : AbstractCartesianDiagram::Private()
227 {
228 }
229 
230 StockDiagram::Private::Private( const Private& r )
231  : AbstractCartesianDiagram::Private( r )
232 {
233 }
234 
235 StockDiagram::Private::~Private()
236 {
237 }
238 
246 QPointF StockDiagram::Private::projectPoint( PaintContext *context, const QPointF &point ) const
247 {
248  return context->coordinatePlane()->translate( QPointF( point.x() + 0.5, point.y() ) );
249 }
250 
257 QRectF StockDiagram::Private::projectCandlestick( PaintContext *context, const QPointF &open, const QPointF &close, qreal width ) const
258 {
259  const QPointF leftHighPoint = context->coordinatePlane()->translate( QPointF( close.x() + 0.5 - width / 2.0, close.y() ) );
260  const QPointF rightLowPoint = context->coordinatePlane()->translate( QPointF( open.x() + 0.5 + width / 2.0, open.y() ) );
261  const QPointF rightHighPoint = context->coordinatePlane()->translate( QPointF( close.x() + 0.5 + width / 2.0, close.y() ) );
262 
263  return QRectF( leftHighPoint, QSizeF( rightHighPoint.x() - leftHighPoint.x(),
264  rightLowPoint.y() - leftHighPoint.y() ) );
265 }
266 
267 void StockDiagram::Private::drawOHLCBar( int dataset, const CartesianDiagramDataCompressor::DataPoint &open,
268  const CartesianDiagramDataCompressor::DataPoint &high,
269  const CartesianDiagramDataCompressor::DataPoint &low,
270  const CartesianDiagramDataCompressor::DataPoint &close,
271  PaintContext *context )
272 {
273  // Note: A row in the model is a column in a StockDiagram
274  const int col = low.index.row();
275 
276  StockBarAttributes attr = stockDiagram()->stockBarAttributes( col );
277  ThreeDBarAttributes threeDAttr = stockDiagram()->threeDBarAttributes( col );
278  const qreal tickLength = attr.tickLength();
279 
280  const QPointF leftOpenPoint( open.key + 0.5 - tickLength, open.value );
281  const QPointF rightOpenPoint( open.key + 0.5, open.value );
282  const QPointF highPoint( high.key + 0.5, high.value );
283  const QPointF lowPoint( low.key + 0.5, low.value );
284  const QPointF leftClosePoint( close.key + 0.5, close.value );
285  const QPointF rightClosePoint( close.key + 0.5 + tickLength, close.value );
286 
287  bool reversedOrder = false;
288  // If 3D mode is enabled, we have to make sure the z-order is right
289  if ( threeDAttr.isEnabled() ) {
290  const int angle = threeDAttr.angle();
291  // Z-order is from right to left
292  if ( ( angle >= 0 && angle < 90 ) || ( angle >= 180 && angle < 270 ) )
293  reversedOrder = true;
294  // Z-order is from left to right
295  if ( ( angle >= 90 && angle < 180 ) || ( angle >= 270 && angle <= 360 ) )
296  reversedOrder = false;
297  }
298 
299  if ( reversedOrder ) {
300  if ( !open.hidden )
301  drawLine( dataset, col, leftOpenPoint, rightOpenPoint, context ); // Open marker
302  if ( !low.hidden && !high.hidden )
303  drawLine( dataset, col, lowPoint, highPoint, context ); // Low-High line
304  if ( !close.hidden )
305  drawLine( dataset, col, leftClosePoint, rightClosePoint, context ); // Close marker
306  } else {
307  if ( !close.hidden )
308  drawLine( dataset, col, leftClosePoint, rightClosePoint, context ); // Close marker
309  if ( !low.hidden && !high.hidden )
310  drawLine( dataset, col, lowPoint, highPoint, context ); // Low-High line
311  if ( !open.hidden )
312  drawLine( dataset, col, leftOpenPoint, rightOpenPoint, context ); // Open marker
313  }
314 
315  LabelPaintCache lpc;
316  if ( !open.hidden ) {
317  addLabel( &lpc, diagram->attributesModel()->mapToSource( open.index ), 0,
318  PositionPoints( leftOpenPoint ), Position::South, Position::South, open.value );
319  }
320  if ( !high.hidden ) {
321  addLabel( &lpc, diagram->attributesModel()->mapToSource( high.index ), 0,
322  PositionPoints( highPoint ), Position::South, Position::South, high.value );
323  }
324  if ( !low.hidden ) {
325  addLabel( &lpc, diagram->attributesModel()->mapToSource( low.index ), 0,
327  }
328  if ( !close.hidden ) {
329  addLabel( &lpc, diagram->attributesModel()->mapToSource( close.index ), 0,
330  PositionPoints( rightClosePoint ), Position::South, Position::South, close.value );
331  }
332  paintDataValueTextsAndMarkers( context, lpc, false );
333 }
334 
342 void StockDiagram::Private::drawCandlestick( int /*dataset*/, const CartesianDiagramDataCompressor::DataPoint &open,
343  const CartesianDiagramDataCompressor::DataPoint &high,
344  const CartesianDiagramDataCompressor::DataPoint &low,
345  const CartesianDiagramDataCompressor::DataPoint &close,
346  PaintContext *context )
347 {
348  PainterSaver painterSaver( context->painter() );
349 
350  // Note: A row in the model is a column in a StockDiagram, and the other way around
351  const int row = low.index.row();
352  const int col = low.index.column();
353 
354  QPointF bottomCandlestickPoint;
355  QPointF topCandlestickPoint;
356  QBrush brush;
357  QPen pen;
358  bool drawLowerLine;
359  bool drawCandlestick = !open.hidden && !close.hidden;
360  bool drawUpperLine;
361 
362  // Find out if we need to paint a down-trend or up-trend candlestick
363  // and set brush and pen accordingly
364  // Also, determine what the top and bottom points of the candlestick are
365  if ( open.value <= close.value ) {
366  pen = stockDiagram()->upTrendCandlestickPen( row );
367  brush = stockDiagram()->upTrendCandlestickBrush( row );
368  bottomCandlestickPoint = QPointF( open.key, open.value );
369  topCandlestickPoint = QPointF( close.key, close.value );
370  drawLowerLine = !low.hidden && !open.hidden;
371  drawUpperLine = !low.hidden && !close.hidden;
372  } else {
373  pen = stockDiagram()->downTrendCandlestickPen( row );
374  brush = stockDiagram()->downTrendCandlestickBrush( row );
375  bottomCandlestickPoint = QPointF( close.key, close.value );
376  topCandlestickPoint = QPointF( open.key, open.value );
377  drawLowerLine = !low.hidden && !close.hidden;
378  drawUpperLine = !low.hidden && !open.hidden;
379  }
380 
381  StockBarAttributes attr = stockDiagram()->stockBarAttributes( col );
382  ThreeDBarAttributes threeDAttr = stockDiagram()->threeDBarAttributes( col );
383 
384  const QPointF lowPoint = projectPoint( context, QPointF( low.key, low.value ) );
385  const QPointF highPoint = projectPoint( context, QPointF( high.key, high.value ) );
386  const QLineF lowerLine = QLineF( lowPoint, projectPoint( context, bottomCandlestickPoint ) );
387  const QLineF upperLine = QLineF( projectPoint( context, topCandlestickPoint ), highPoint );
388 
389  // Convert the data point into coordinates on the coordinate plane
390  QRectF candlestick = projectCandlestick( context, bottomCandlestickPoint,
391  topCandlestickPoint, attr.candlestickWidth() );
392 
393  // Remember the drawn polygon to add it to the ReverseMapper later
394  QPolygonF drawnPolygon;
395 
396  // Use the ThreeDPainter class to draw a 3D candlestick
397  if ( threeDAttr.isEnabled() ) {
398  ThreeDPainter threeDPainter( context->painter() );
399 
400  ThreeDPainter::ThreeDProperties threeDProps;
401  threeDProps.depth = threeDAttr.depth();
402  threeDProps.angle = threeDAttr.angle();
403  threeDProps.useShadowColors = threeDAttr.useShadowColors();
404 
405  // If the perspective angle is within [0,180], we paint from bottom to top,
406  // otherwise from top to bottom to ensure the correct z order
407  if ( threeDProps.angle > 0.0 && threeDProps.angle < 180.0 ) {
408  if ( drawLowerLine )
409  drawnPolygon = threeDPainter.drawTwoDLine( lowerLine, pen, threeDProps );
410  if ( drawCandlestick )
411  drawnPolygon = threeDPainter.drawThreeDRect( candlestick, brush, pen, threeDProps );
412  if ( drawUpperLine )
413  drawnPolygon = threeDPainter.drawTwoDLine( upperLine, pen, threeDProps );
414  } else {
415  if ( drawUpperLine )
416  drawnPolygon = threeDPainter.drawTwoDLine( upperLine, pen, threeDProps );
417  if ( drawCandlestick )
418  drawnPolygon = threeDPainter.drawThreeDRect( candlestick, brush, pen, threeDProps );
419  if ( drawLowerLine )
420  drawnPolygon = threeDPainter.drawTwoDLine( lowerLine, pen, threeDProps );
421  }
422  } else {
423  QPainter *const painter = context->painter();
424  painter->setBrush( brush );
425  painter->setPen( pen );
426  if ( drawLowerLine )
427  painter->drawLine( lowerLine );
428  if ( drawUpperLine )
429  painter->drawLine( upperLine );
430  if ( drawCandlestick )
431  painter->drawRect( candlestick );
432 
433  // The 2D representation is the projected candlestick itself
434  drawnPolygon = candlestick;
435 
436  // FIXME: Add lower and upper line to reverse mapper
437  }
438 
439  LabelPaintCache lpc;
440  if ( !low.hidden )
441  addLabel( &lpc, diagram->attributesModel()->mapToSource( low.index ), 0,
443  if ( drawCandlestick ) {
444  // Both, the open as well as the close value are represented by this candlestick
445  reverseMapper.addPolygon( row, openValueColumn(), drawnPolygon );
446  reverseMapper.addPolygon( row, closeValueColumn(), drawnPolygon );
447 
448  addLabel( &lpc, diagram->attributesModel()->mapToSource( open.index ), 0,
449  PositionPoints( candlestick.bottomRight() ), Position::South, Position::South, open.value );
450  addLabel( &lpc, diagram->attributesModel()->mapToSource( close.index ), 0,
451  PositionPoints( candlestick.topRight() ), Position::South, Position::South, close.value );
452  }
453  if ( !high.hidden )
454  addLabel( &lpc, diagram->attributesModel()->mapToSource( high.index ), 0,
455  PositionPoints( highPoint ), Position::South, Position::South, high.value );
456 
457  paintDataValueTextsAndMarkers( context, lpc, false );
458 }
459 
468 void StockDiagram::Private::drawLine( int dataset, int col, const QPointF &point1, const QPointF &point2, PaintContext *context )
469 {
470  PainterSaver painterSaver( context->painter() );
471 
472  // A row in the model is a column in the diagram
473  const int modelRow = col;
474  const int modelCol = 0;
475 
476  const QPen pen = diagram->pen( dataset );
477  const QBrush brush = diagram->brush( dataset );
478  const ThreeDBarAttributes threeDBarAttr = stockDiagram()->threeDBarAttributes( col );
479 
480  QPointF transP1 = context->coordinatePlane()->translate( point1 );
481  QPointF transP2 = context->coordinatePlane()->translate( point2 );
482  QLineF line = QLineF( transP1, transP2 );
483 
484  if ( threeDBarAttr.isEnabled() ) {
485  ThreeDPainter::ThreeDProperties threeDProps;
486  threeDProps.angle = threeDBarAttr.angle();
487  threeDProps.depth = threeDBarAttr.depth();
488  threeDProps.useShadowColors = threeDBarAttr.useShadowColors();
489 
490  ThreeDPainter painter( context->painter() );
491  reverseMapper.addPolygon( modelCol, modelRow, painter.drawThreeDLine( line, brush, pen, threeDProps ) );
492  } else {
493  context->painter()->setPen( pen );
494  //context->painter()->setBrush( brush );
495  reverseMapper.addLine( modelCol, modelRow, transP1, transP2 );
496  context->painter()->drawLine( line );
497  }
498 }
499 
505 int StockDiagram::Private::openValueColumn() const
506 {
507  // Return an invalid column if diagram has no open values
508  return type == HighLowClose ? -1 : 0;
509 }
510 
516 int StockDiagram::Private::highValueColumn() const
517 {
518  return type == HighLowClose ? 0 : 1;
519 }
520 
526 int StockDiagram::Private::lowValueColumn() const
527 {
528  return type == HighLowClose ? 1 : 2;
529 }
530 
536 int StockDiagram::Private::closeValueColumn() const
537 {
538  return type == HighLowClose ? 2 : 3;
539 }
540 
KDChartEnums::PositionValue value() const
Returns an integer value corresponding to this Position.
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 const Position & South
QPainter * painter() const
QBrush brush() const
Retrieve the brush to be used for painting datapoints globally.
Type type() const
Base class for diagrams based on a cartesian coordianate system.
Stores information about painting diagrams.
A set of 3D bar attributes.
Stores the absolute target points of a Position.
QPen pen() const
Retrieve the pen to be used for painting datapoints globally.
Attributes to customize the appearance of a column in a stock chart.

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/