KD Chart 2
[rev.2.5]
|
00001 /**************************************************************************** 00002 ** Copyright (C) 2001-2012 Klaralvdalens Datakonsult AB. All rights reserved. 00003 ** 00004 ** This file is part of the KD Chart library. 00005 ** 00006 ** Licensees holding valid commercial KD Chart licenses may use this file in 00007 ** accordance with the KD Chart Commercial License Agreement provided with 00008 ** the Software. 00009 ** 00010 ** 00011 ** This file may be distributed and/or modified under the terms of the 00012 ** GNU General Public License version 2 and version 3 as published by the 00013 ** Free Software Foundation and appearing in the file LICENSE.GPL.txt included. 00014 ** 00015 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE 00016 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 00017 ** 00018 ** Contact info@kdab.com if any conditions of this licensing are not 00019 ** clear to you. 00020 ** 00021 **********************************************************************/ 00022 00023 #include "KDChartStockDiagram_p.h" 00024 00025 #include "KDChartPainterSaver_p.h" 00026 00027 using namespace KDChart; 00028 00029 00030 class StockDiagram::Private::ThreeDPainter 00031 { 00032 public: 00033 struct ThreeDProperties { 00034 qreal depth; 00035 qreal angle; 00036 bool useShadowColors; 00037 }; 00038 00039 ThreeDPainter( QPainter *p ) 00040 : painter( p ) {}; 00041 00042 QPolygonF drawTwoDLine( const QLineF &line, const QPen &pen, 00043 const ThreeDProperties &props ); 00044 QPolygonF drawThreeDLine( const QLineF &line, const QBrush &brush, 00045 const QPen &pen, const ThreeDProperties &props ); 00046 QPolygonF drawThreeDRect( const QRectF &rect, const QBrush &brush, 00047 const QPen &pen, const ThreeDProperties &props ); 00048 00049 private: 00050 QPointF projectPoint( const QPointF &point, qreal depth, qreal angle ) const; 00051 QColor calcShadowColor( const QColor &color, qreal angle ) const; 00052 00053 QPainter *painter; 00054 }; 00055 00062 QPointF StockDiagram::Private::ThreeDPainter::projectPoint( const QPointF &point, qreal depth, qreal angle ) const 00063 { 00064 const qreal angleInRad = DEGTORAD( angle ); 00065 const qreal distX = depth * cos( angleInRad ); 00066 // Y coordinates are reversed on our coordinate plane 00067 const qreal distY = depth * -sin( angleInRad ); 00068 00069 return QPointF( point.x() + distX, point.y() + distY ); 00070 } 00071 00078 QColor StockDiagram::Private::ThreeDPainter::calcShadowColor( const QColor &color, qreal angle ) const 00079 { 00080 // The shadow factor determines to how many percent the brightness 00081 // of the color can be reduced. That is, the darkest shadow color 00082 // is color * shadowFactor. 00083 const qreal shadowFactor = 0.5; 00084 const qreal sinAngle = 1.0 - qAbs( sin( DEGTORAD( angle ) ) ) * shadowFactor; 00085 return QColor( qRound( color.red() * sinAngle ), 00086 qRound( color.green() * sinAngle ), 00087 qRound( color.blue() * sinAngle ) ); 00088 } 00089 00098 QPolygonF StockDiagram::Private::ThreeDPainter::drawTwoDLine( const QLineF &line, const QPen &pen, 00099 const ThreeDProperties &props ) 00100 { 00101 // Restores the painting properties when destroyed 00102 PainterSaver painterSaver( painter ); 00103 00104 // The z coordinate to use (i.e., at what depth to draw the line) 00105 const qreal z = props.depth / 2.0; 00106 00107 // Projec the 2D points of the line in 3D 00108 const QPointF deepP1 = projectPoint( line.p1(), z, props.angle ); 00109 const QPointF deepP2 = projectPoint( line.p2(), z, props.angle ); 00110 00111 // The drawn line with a width of 2px 00112 QPolygonF threeDArea; 00113 // The offset of the line "borders" from the center to each side 00114 const QPointF offset( 0.0, 1.0 ); 00115 threeDArea << deepP1 - offset << deepP2 - offset 00116 << deepP1 + offset << deepP2 + offset << deepP1 - offset; 00117 00118 painter->setPen( pen ); 00119 painter->drawLine( QLineF( deepP1, deepP2 ) ); 00120 00121 return threeDArea; 00122 } 00123 00133 QPolygonF StockDiagram::Private::ThreeDPainter::drawThreeDLine( const QLineF &line, const QBrush &brush, 00134 const QPen &pen, const ThreeDProperties &props ) 00135 { 00136 // Restores the painting properties when destroyed 00137 PainterSaver painterSaver( painter ); 00138 00139 const QPointF p1 = line.p1(); 00140 const QPointF p2 = line.p2(); 00141 00142 // Project the 2D points of the line in 3D 00143 const QPointF deepP1 = projectPoint( p1, props.depth, props.angle ); 00144 const QPointF deepP2 = projectPoint( p2, props.depth, props.angle ); 00145 00146 // The result is a 3D representation of the 2D line 00147 QPolygonF threeDArea; 00148 threeDArea << p1 << p2 << deepP2 << deepP1 << p1; 00149 00150 // Use shadow colors if ThreeDProperties::useShadowColors is set 00151 // Note: Setting a new color on a brush or pen does not effect gradients or textures 00152 if ( props.useShadowColors ) { 00153 QBrush shadowBrush( brush ); 00154 QPen shadowPen( pen ); 00155 shadowBrush.setColor( calcShadowColor( brush.color(), props.angle ) ); 00156 shadowPen.setColor( calcShadowColor( pen.color(), props.angle ) ); 00157 painter->setBrush( shadowBrush ); 00158 painter->setPen( shadowPen ); 00159 } else { 00160 painter->setBrush( brush ); 00161 painter->setPen( pen ); 00162 } 00163 00164 painter->drawPolygon( threeDArea ); 00165 00166 return threeDArea; 00167 } 00168 00178 QPolygonF StockDiagram::Private::ThreeDPainter::drawThreeDRect( const QRectF &rect, const QBrush &brush, 00179 const QPen &pen, const ThreeDProperties &props ) 00180 { 00181 // Restores the painting properties when destroyed 00182 PainterSaver painterSaver( painter ); 00183 00184 // Make sure that the top really is the top 00185 const QRectF normalizedRect = rect.normalized(); 00186 00187 // Calculate all the four sides of the rectangle 00188 const QLineF topSide = QLineF( normalizedRect.topLeft(), normalizedRect.topRight() ); 00189 const QLineF bottomSide = QLineF( normalizedRect.bottomLeft(), normalizedRect.bottomRight() ); 00190 const QLineF leftSide = QLineF( normalizedRect.topLeft(), normalizedRect.bottomLeft() ); 00191 const QLineF rightSide = QLineF( normalizedRect.topRight(), normalizedRect.bottomRight() ); 00192 00193 QPolygonF drawnPolygon; 00194 00195 // Shorter names are easier on the eyes 00196 const qreal angle = props.angle; 00197 00198 // Only top and right side is visible 00199 if ( angle >= 0.0 && angle < 90.0 ) { 00200 drawnPolygon = drawnPolygon.united( drawThreeDLine( topSide, brush, pen, props ) ); 00201 drawnPolygon = drawnPolygon.united( drawThreeDLine( rightSide, brush, pen, props ) ); 00202 // Only top and left side is visible 00203 } else if ( angle >= 90.0 && angle < 180.0 ) { 00204 drawnPolygon = drawnPolygon.united( drawThreeDLine( topSide, brush, pen, props ) ); 00205 drawnPolygon = drawnPolygon.united( drawThreeDLine( leftSide, brush, pen, props ) ); 00206 // Only bottom and left side is visible 00207 } else if ( angle >= 180.0 && angle < 270.0 ) { 00208 drawnPolygon = drawnPolygon.united( drawThreeDLine( bottomSide, brush, pen, props ) ); 00209 drawnPolygon = drawnPolygon.united( drawThreeDLine( leftSide, brush, pen, props ) ); 00210 // Only bottom and right side is visible 00211 } else if ( angle >= 270.0 && angle <= 360.0 ) { 00212 drawnPolygon = drawnPolygon.united( drawThreeDLine( bottomSide, brush, pen, props ) ); 00213 drawnPolygon = drawnPolygon.united( drawThreeDLine( rightSide, brush, pen, props ) ); 00214 } 00215 00216 // Draw the front side 00217 painter->setPen( pen ); 00218 painter->setBrush( brush ); 00219 painter->drawRect( normalizedRect ); 00220 00221 return drawnPolygon; 00222 } 00223 00224 00225 StockDiagram::Private::Private() 00226 : AbstractCartesianDiagram::Private() 00227 { 00228 } 00229 00230 StockDiagram::Private::Private( const Private& r ) 00231 : AbstractCartesianDiagram::Private( r ) 00232 { 00233 } 00234 00235 StockDiagram::Private::~Private() 00236 { 00237 } 00238 00246 QPointF StockDiagram::Private::projectPoint( PaintContext *context, const QPointF &point ) const 00247 { 00248 return context->coordinatePlane()->translate( QPointF( point.x() + 0.5, point.y() ) ); 00249 } 00250 00257 QRectF StockDiagram::Private::projectCandlestick( PaintContext *context, const QPointF &open, const QPointF &close, qreal width ) const 00258 { 00259 const QPointF leftHighPoint = context->coordinatePlane()->translate( QPointF( close.x() + 0.5 - width / 2.0, close.y() ) ); 00260 const QPointF rightLowPoint = context->coordinatePlane()->translate( QPointF( open.x() + 0.5 + width / 2.0, open.y() ) ); 00261 const QPointF rightHighPoint = context->coordinatePlane()->translate( QPointF( close.x() + 0.5 + width / 2.0, close.y() ) ); 00262 00263 return QRectF( leftHighPoint, QSizeF( rightHighPoint.x() - leftHighPoint.x(), 00264 rightLowPoint.y() - leftHighPoint.y() ) ); 00265 } 00266 00267 void StockDiagram::Private::drawOHLCBar( int dataset, const CartesianDiagramDataCompressor::DataPoint &open, 00268 const CartesianDiagramDataCompressor::DataPoint &high, 00269 const CartesianDiagramDataCompressor::DataPoint &low, 00270 const CartesianDiagramDataCompressor::DataPoint &close, 00271 PaintContext *context ) 00272 { 00273 // Note: A row in the model is a column in a StockDiagram 00274 const int col = low.index.row(); 00275 00276 StockBarAttributes attr = stockDiagram()->stockBarAttributes( col ); 00277 ThreeDBarAttributes threeDAttr = stockDiagram()->threeDBarAttributes( col ); 00278 const qreal tickLength = attr.tickLength(); 00279 00280 const QPointF leftOpenPoint( open.key + 0.5 - tickLength, open.value ); 00281 const QPointF rightOpenPoint( open.key + 0.5, open.value ); 00282 const QPointF highPoint( high.key + 0.5, high.value ); 00283 const QPointF lowPoint( low.key + 0.5, low.value ); 00284 const QPointF leftClosePoint( close.key + 0.5, close.value ); 00285 const QPointF rightClosePoint( close.key + 0.5 + tickLength, close.value ); 00286 00287 bool reversedOrder = false; 00288 // If 3D mode is enabled, we have to make sure the z-order is right 00289 if ( threeDAttr.isEnabled() ) { 00290 const int angle = threeDAttr.angle(); 00291 // Z-order is from right to left 00292 if ( ( angle >= 0 && angle < 90 ) || ( angle >= 180 && angle < 270 ) ) 00293 reversedOrder = true; 00294 // Z-order is from left to right 00295 if ( ( angle >= 90 && angle < 180 ) || ( angle >= 270 && angle < 0 ) ) 00296 reversedOrder = false; 00297 } 00298 00299 if ( reversedOrder ) { 00300 if ( !open.hidden ) 00301 drawLine( dataset, col, leftOpenPoint, rightOpenPoint, context ); // Open marker 00302 if ( !low.hidden && !high.hidden ) 00303 drawLine( dataset, col, lowPoint, highPoint, context ); // Low-High line 00304 if ( !close.hidden ) 00305 drawLine( dataset, col, leftClosePoint, rightClosePoint, context ); // Close marker 00306 } else { 00307 if ( !close.hidden ) 00308 drawLine( dataset, col, leftClosePoint, rightClosePoint, context ); // Close marker 00309 if ( !low.hidden && !high.hidden ) 00310 drawLine( dataset, col, lowPoint, highPoint, context ); // Low-High line 00311 if ( !open.hidden ) 00312 drawLine( dataset, col, leftOpenPoint, rightOpenPoint, context ); // Open marker 00313 } 00314 00315 LabelPaintCache lpc; 00316 if ( !open.hidden ) { 00317 addLabel( &lpc, diagram->attributesModel()->mapToSource( open.index ), 0, 00318 PositionPoints( leftOpenPoint ), Position::South, Position::South, open.value ); 00319 } 00320 if ( !high.hidden ) { 00321 addLabel( &lpc, diagram->attributesModel()->mapToSource( high.index ), 0, 00322 PositionPoints( highPoint ), Position::South, Position::South, high.value ); 00323 } 00324 if ( !low.hidden ) { 00325 addLabel( &lpc, diagram->attributesModel()->mapToSource( low.index ), 0, 00326 PositionPoints( lowPoint ), Position::South, Position::South, low.value ); 00327 } 00328 if ( !close.hidden ) { 00329 addLabel( &lpc, diagram->attributesModel()->mapToSource( close.index ), 0, 00330 PositionPoints( rightClosePoint ), Position::South, Position::South, close.value ); 00331 } 00332 paintDataValueTextsAndMarkers( context, lpc, false ); 00333 } 00334 00342 void StockDiagram::Private::drawCandlestick( int /*dataset*/, const CartesianDiagramDataCompressor::DataPoint &open, 00343 const CartesianDiagramDataCompressor::DataPoint &high, 00344 const CartesianDiagramDataCompressor::DataPoint &low, 00345 const CartesianDiagramDataCompressor::DataPoint &close, 00346 PaintContext *context ) 00347 { 00348 PainterSaver painterSaver( context->painter() ); 00349 00350 // Note: A row in the model is a column in a StockDiagram, and the other way around 00351 const int row = low.index.row(); 00352 const int col = low.index.column(); 00353 00354 QPointF bottomCandlestickPoint; 00355 QPointF topCandlestickPoint; 00356 QBrush brush; 00357 QPen pen; 00358 bool drawLowerLine; 00359 bool drawCandlestick = !open.hidden && !close.hidden; 00360 bool drawUpperLine; 00361 00362 // Find out if we need to paint a down-trend or up-trend candlestick 00363 // and set brush and pen accordingly 00364 // Also, determine what the top and bottom points of the candlestick are 00365 if ( open.value <= close.value ) { 00366 pen = stockDiagram()->upTrendCandlestickPen( row ); 00367 brush = stockDiagram()->upTrendCandlestickBrush( row ); 00368 bottomCandlestickPoint = QPointF( open.key, open.value ); 00369 topCandlestickPoint = QPointF( close.key, close.value ); 00370 drawLowerLine = !low.hidden && !open.hidden; 00371 drawUpperLine = !low.hidden && !close.hidden; 00372 } else { 00373 pen = stockDiagram()->downTrendCandlestickPen( row ); 00374 brush = stockDiagram()->downTrendCandlestickBrush( row ); 00375 bottomCandlestickPoint = QPointF( close.key, close.value ); 00376 topCandlestickPoint = QPointF( open.key, open.value ); 00377 drawLowerLine = !low.hidden && !close.hidden; 00378 drawUpperLine = !low.hidden && !open.hidden; 00379 } 00380 00381 StockBarAttributes attr = stockDiagram()->stockBarAttributes( col ); 00382 ThreeDBarAttributes threeDAttr = stockDiagram()->threeDBarAttributes( col ); 00383 00384 const QPointF lowPoint = projectPoint( context, QPointF( low.key, low.value ) ); 00385 const QPointF highPoint = projectPoint( context, QPointF( high.key, high.value ) ); 00386 const QLineF lowerLine = QLineF( lowPoint, projectPoint( context, bottomCandlestickPoint ) ); 00387 const QLineF upperLine = QLineF( projectPoint( context, topCandlestickPoint ), highPoint ); 00388 00389 // Convert the data point into coordinates on the coordinate plane 00390 QRectF candlestick = projectCandlestick( context, bottomCandlestickPoint, 00391 topCandlestickPoint, attr.candlestickWidth() ); 00392 00393 // Remember the drawn polygon to add it to the ReverseMapper later 00394 QPolygonF drawnPolygon; 00395 00396 // Use the ThreeDPainter class to draw a 3D candlestick 00397 if ( threeDAttr.isEnabled() ) { 00398 ThreeDPainter threeDPainter( context->painter() ); 00399 00400 ThreeDPainter::ThreeDProperties threeDProps; 00401 threeDProps.depth = threeDAttr.depth(); 00402 threeDProps.angle = threeDAttr.angle(); 00403 threeDProps.useShadowColors = threeDAttr.useShadowColors(); 00404 00405 // If the perspective angle is within [0,180], we paint from bottom to top, 00406 // otherwise from top to bottom to ensure the correct z order 00407 if ( threeDProps.angle > 0.0 && threeDProps.angle < 180.0 ) { 00408 if ( drawLowerLine ) 00409 drawnPolygon = threeDPainter.drawTwoDLine( lowerLine, pen, threeDProps ); 00410 if ( drawCandlestick ) 00411 drawnPolygon = threeDPainter.drawThreeDRect( candlestick, brush, pen, threeDProps ); 00412 if ( drawUpperLine ) 00413 drawnPolygon = threeDPainter.drawTwoDLine( upperLine, pen, threeDProps ); 00414 } else { 00415 if ( drawUpperLine ) 00416 drawnPolygon = threeDPainter.drawTwoDLine( upperLine, pen, threeDProps ); 00417 if ( drawCandlestick ) 00418 drawnPolygon = threeDPainter.drawThreeDRect( candlestick, brush, pen, threeDProps ); 00419 if ( drawLowerLine ) 00420 drawnPolygon = threeDPainter.drawTwoDLine( lowerLine, pen, threeDProps ); 00421 } 00422 } else { 00423 QPainter *const painter = context->painter(); 00424 painter->setBrush( brush ); 00425 painter->setPen( pen ); 00426 if ( drawLowerLine ) 00427 painter->drawLine( lowerLine ); 00428 if ( drawUpperLine ) 00429 painter->drawLine( upperLine ); 00430 if ( drawCandlestick ) 00431 painter->drawRect( candlestick ); 00432 00433 // The 2D representation is the projected candlestick itself 00434 drawnPolygon = candlestick; 00435 00436 // FIXME: Add lower and upper line to reverse mapper 00437 } 00438 00439 LabelPaintCache lpc; 00440 if ( !low.hidden ) 00441 addLabel( &lpc, diagram->attributesModel()->mapToSource( low.index ), 0, 00442 PositionPoints( lowPoint ), Position::South, Position::South, low.value ); 00443 if ( drawCandlestick ) { 00444 // Both, the open as well as the close value are represented by this candlestick 00445 reverseMapper.addPolygon( row, openValueColumn(), drawnPolygon ); 00446 reverseMapper.addPolygon( row, closeValueColumn(), drawnPolygon ); 00447 00448 addLabel( &lpc, diagram->attributesModel()->mapToSource( open.index ), 0, 00449 PositionPoints( candlestick.bottomRight() ), Position::South, Position::South, open.value ); 00450 addLabel( &lpc, diagram->attributesModel()->mapToSource( close.index ), 0, 00451 PositionPoints( candlestick.topRight() ), Position::South, Position::South, close.value ); 00452 } 00453 if ( !high.hidden ) 00454 addLabel( &lpc, diagram->attributesModel()->mapToSource( high.index ), 0, 00455 PositionPoints( highPoint ), Position::South, Position::South, high.value ); 00456 00457 paintDataValueTextsAndMarkers( context, lpc, false ); 00458 } 00459 00468 void StockDiagram::Private::drawLine( int dataset, int col, const QPointF &point1, const QPointF &point2, PaintContext *context ) 00469 { 00470 PainterSaver painterSaver( context->painter() ); 00471 00472 // A row in the model is a column in the diagram 00473 const int modelRow = col; 00474 const int modelCol = 0; 00475 00476 const QPen pen = diagram->pen( dataset ); 00477 const QBrush brush = diagram->brush( dataset ); 00478 const ThreeDBarAttributes threeDBarAttr = stockDiagram()->threeDBarAttributes( col ); 00479 00480 QPointF transP1 = context->coordinatePlane()->translate( point1 ); 00481 QPointF transP2 = context->coordinatePlane()->translate( point2 ); 00482 QLineF line = QLineF( transP1, transP2 ); 00483 00484 if ( threeDBarAttr.isEnabled() ) { 00485 ThreeDPainter::ThreeDProperties threeDProps; 00486 threeDProps.angle = threeDBarAttr.angle(); 00487 threeDProps.depth = threeDBarAttr.depth(); 00488 threeDProps.useShadowColors = threeDBarAttr.useShadowColors(); 00489 00490 ThreeDPainter painter( context->painter() ); 00491 reverseMapper.addPolygon( modelCol, modelRow, painter.drawThreeDLine( line, brush, pen, threeDProps ) ); 00492 } else { 00493 context->painter()->setPen( pen ); 00494 //context->painter()->setBrush( brush ); 00495 reverseMapper.addLine( modelCol, modelRow, transP1, transP2 ); 00496 context->painter()->drawLine( line ); 00497 } 00498 } 00499 00505 int StockDiagram::Private::openValueColumn() const 00506 { 00507 // Return an invalid column if diagram has no open values 00508 return type == HighLowClose ? -1 : 0; 00509 } 00510 00516 int StockDiagram::Private::highValueColumn() const 00517 { 00518 return type == HighLowClose ? 0 : 1; 00519 } 00520 00526 int StockDiagram::Private::lowValueColumn() const 00527 { 00528 return type == HighLowClose ? 1 : 2; 00529 } 00530 00536 int StockDiagram::Private::closeValueColumn() const 00537 { 00538 return type == HighLowClose ? 2 : 3; 00539 } 00540