KD Chart 2 [rev.2.4]
|
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 using namespace KDChart; 00026 00027 00028 class StockDiagram::Private::ThreeDPainter 00029 { 00030 public: 00031 struct ThreeDProperties { 00032 qreal depth; 00033 qreal angle; 00034 bool useShadowColors; 00035 }; 00036 00037 ThreeDPainter( QPainter *p ) 00038 : painter( p ) {}; 00039 00040 QPolygonF drawTwoDLine( const QLineF &line, const QPen &pen, 00041 const ThreeDProperties &props ); 00042 QPolygonF drawThreeDLine( const QLineF &line, const QBrush &brush, 00043 const QPen &pen, const ThreeDProperties &props ); 00044 QPolygonF drawThreeDRect( const QRectF &rect, const QBrush &brush, 00045 const QPen &pen, const ThreeDProperties &props ); 00046 00047 private: 00048 QPointF projectPoint( const QPointF &point, qreal depth, qreal angle ) const; 00049 QColor calcShadowColor( const QColor &color, qreal angle ) const; 00050 00051 QPainter *painter; 00052 }; 00053 00060 QPointF StockDiagram::Private::ThreeDPainter::projectPoint( const QPointF &point, qreal depth, qreal angle ) const 00061 { 00062 const qreal angleInRad = DEGTORAD( angle ); 00063 const qreal distX = depth * cos( angleInRad ); 00064 // Y coordinates are reversed on our coordinate plane 00065 const qreal distY = depth * -sin( angleInRad ); 00066 00067 return QPointF( point.x() + distX, point.y() + distY ); 00068 } 00069 00076 QColor StockDiagram::Private::ThreeDPainter::calcShadowColor( const QColor &color, qreal angle ) const 00077 { 00078 // The shadow factor determines to how many percent the brightness 00079 // of the color can be reduced. That is, the darkest shadow color 00080 // is color * shadowFactor. 00081 const qreal shadowFactor = 0.5; 00082 const qreal sinAngle = 1.0 - qAbs( sin( DEGTORAD( angle ) ) ) * shadowFactor; 00083 return QColor( qRound( color.red() * sinAngle ), 00084 qRound( color.green() * sinAngle ), 00085 qRound( color.blue() * sinAngle ) ); 00086 } 00087 00096 QPolygonF StockDiagram::Private::ThreeDPainter::drawTwoDLine( const QLineF &line, const QPen &pen, 00097 const ThreeDProperties &props ) 00098 { 00099 // Restores the painting properties when destroyed 00100 PainterSaver painterSaver( painter ); 00101 00102 // The z coordinate to use (i.e., at what depth to draw the line) 00103 const qreal z = props.depth / 2.0; 00104 00105 // Projec the 2D points of the line in 3D 00106 const QPointF deepP1 = projectPoint( line.p1(), z, props.angle ); 00107 const QPointF deepP2 = projectPoint( line.p2(), z, props.angle ); 00108 00109 // The drawn line with a width of 2px 00110 QPolygonF threeDArea; 00111 // The offset of the line "borders" from the center to each side 00112 const QPointF offset( 0.0, 1.0 ); 00113 threeDArea << deepP1 - offset << deepP2 - offset 00114 << deepP1 + offset << deepP2 + offset << deepP1 - offset; 00115 00116 painter->setPen( pen ); 00117 painter->drawLine( QLineF( deepP1, deepP2 ) ); 00118 00119 return threeDArea; 00120 } 00121 00131 QPolygonF StockDiagram::Private::ThreeDPainter::drawThreeDLine( const QLineF &line, const QBrush &brush, 00132 const QPen &pen, const ThreeDProperties &props ) 00133 { 00134 // Restores the painting properties when destroyed 00135 PainterSaver painterSaver( painter ); 00136 00137 const QPointF p1 = line.p1(); 00138 const QPointF p2 = line.p2(); 00139 00140 // Project the 2D points of the line in 3D 00141 const QPointF deepP1 = projectPoint( p1, props.depth, props.angle ); 00142 const QPointF deepP2 = projectPoint( p2, props.depth, props.angle ); 00143 00144 // The result is a 3D representation of the 2D line 00145 QPolygonF threeDArea; 00146 threeDArea << p1 << p2 << deepP2 << deepP1 << p1; 00147 00148 // Use shadow colors if ThreeDProperties::useShadowColors is set 00149 // Note: Setting a new color on a brush or pen does not effect gradients or textures 00150 if ( props.useShadowColors ) { 00151 QBrush shadowBrush( brush ); 00152 QPen shadowPen( pen ); 00153 shadowBrush.setColor( calcShadowColor( brush.color(), props.angle ) ); 00154 shadowPen.setColor( calcShadowColor( pen.color(), props.angle ) ); 00155 painter->setBrush( shadowBrush ); 00156 painter->setPen( shadowPen ); 00157 } else { 00158 painter->setBrush( brush ); 00159 painter->setPen( pen ); 00160 } 00161 00162 painter->drawPolygon( threeDArea ); 00163 00164 return threeDArea; 00165 } 00166 00176 QPolygonF StockDiagram::Private::ThreeDPainter::drawThreeDRect( const QRectF &rect, const QBrush &brush, 00177 const QPen &pen, const ThreeDProperties &props ) 00178 { 00179 // Restores the painting properties when destroyed 00180 PainterSaver painterSaver( painter ); 00181 00182 // Make sure that the top really is the top 00183 const QRectF normalizedRect = rect.normalized(); 00184 00185 // Calculate all the four sides of the rectangle 00186 const QLineF topSide = QLineF( normalizedRect.topLeft(), normalizedRect.topRight() ); 00187 const QLineF bottomSide = QLineF( normalizedRect.bottomLeft(), normalizedRect.bottomRight() ); 00188 const QLineF leftSide = QLineF( normalizedRect.topLeft(), normalizedRect.bottomLeft() ); 00189 const QLineF rightSide = QLineF( normalizedRect.topRight(), normalizedRect.bottomRight() ); 00190 00191 QPolygonF drawnPolygon; 00192 00193 // Shorter names are easier on the eyes 00194 const qreal angle = props.angle; 00195 00196 // Only top and right side is visible 00197 if ( angle >= 0.0 && angle < 90.0 ) { 00198 drawnPolygon = drawnPolygon.united( drawThreeDLine( topSide, brush, pen, props ) ); 00199 drawnPolygon = drawnPolygon.united( drawThreeDLine( rightSide, brush, pen, props ) ); 00200 // Only top and left side is visible 00201 } else if ( angle >= 90.0 && angle < 180.0 ) { 00202 drawnPolygon = drawnPolygon.united( drawThreeDLine( topSide, brush, pen, props ) ); 00203 drawnPolygon = drawnPolygon.united( drawThreeDLine( leftSide, brush, pen, props ) ); 00204 // Only bottom and left side is visible 00205 } else if ( angle >= 180.0 && angle < 270.0 ) { 00206 drawnPolygon = drawnPolygon.united( drawThreeDLine( bottomSide, brush, pen, props ) ); 00207 drawnPolygon = drawnPolygon.united( drawThreeDLine( leftSide, brush, pen, props ) ); 00208 // Only bottom and right side is visible 00209 } else if ( angle >= 270.0 && angle <= 360.0 ) { 00210 drawnPolygon = drawnPolygon.united( drawThreeDLine( bottomSide, brush, pen, props ) ); 00211 drawnPolygon = drawnPolygon.united( drawThreeDLine( rightSide, brush, pen, props ) ); 00212 } 00213 00214 // Draw the front side 00215 painter->setPen( pen ); 00216 painter->setBrush( brush ); 00217 painter->drawRect( normalizedRect ); 00218 00219 return drawnPolygon; 00220 } 00221 00222 00223 StockDiagram::Private::Private() 00224 : AbstractCartesianDiagram::Private() 00225 { 00226 } 00227 00228 StockDiagram::Private::Private( const Private& r ) 00229 : AbstractCartesianDiagram::Private( r ) 00230 { 00231 } 00232 00233 StockDiagram::Private::~Private() 00234 { 00235 } 00236 00244 QPointF StockDiagram::Private::projectPoint( PaintContext *context, const QPointF &point ) const 00245 { 00246 return context->coordinatePlane()->translate( QPointF( point.x() + 0.5, point.y() ) ); 00247 } 00248 00255 QRectF StockDiagram::Private::projectCandlestick( PaintContext *context, const QPointF &open, const QPointF &close, qreal width ) const 00256 { 00257 const QPointF leftHighPoint = context->coordinatePlane()->translate( QPointF( close.x() + 0.5 - width / 2.0, close.y() ) ); 00258 const QPointF rightLowPoint = context->coordinatePlane()->translate( QPointF( open.x() + 0.5 + width / 2.0, open.y() ) ); 00259 const QPointF rightHighPoint = context->coordinatePlane()->translate( QPointF( close.x() + 0.5 + width / 2.0, close.y() ) ); 00260 00261 return QRectF( leftHighPoint, QSizeF( rightHighPoint.x() - leftHighPoint.x(), 00262 rightLowPoint.y() - leftHighPoint.y() ) ); 00263 } 00264 00265 void StockDiagram::Private::drawOHLCBar( int dataset, const CartesianDiagramDataCompressor::DataPoint &open, 00266 const CartesianDiagramDataCompressor::DataPoint &high, 00267 const CartesianDiagramDataCompressor::DataPoint &low, 00268 const CartesianDiagramDataCompressor::DataPoint &close, 00269 PaintContext *context ) 00270 { 00271 // Note: A row in the model is a column in a StockDiagram 00272 const int col = low.index.row(); 00273 00274 StockBarAttributes attr = diagram->stockBarAttributes( col ); 00275 ThreeDBarAttributes threeDAttr = diagram->threeDBarAttributes( col ); 00276 const qreal tickLength = attr.tickLength(); 00277 00278 const QPointF leftOpenPoint( open.key + 0.5 - tickLength, open.value ); 00279 const QPointF rightOpenPoint( open.key + 0.5, open.value ); 00280 const QPointF highPoint( high.key + 0.5, high.value ); 00281 const QPointF lowPoint( low.key + 0.5, low.value ); 00282 const QPointF leftClosePoint( close.key + 0.5, close.value ); 00283 const QPointF rightClosePoint( close.key + 0.5 + tickLength, close.value ); 00284 00285 bool reversedOrder = false; 00286 // If 3D mode is enabled, we have to make sure the z-order is right 00287 if ( threeDAttr.isEnabled() ) { 00288 const int angle = threeDAttr.angle(); 00289 // Z-order is from right to left 00290 if ( ( angle >= 0 && angle < 90 ) || ( angle >= 180 && angle < 270 ) ) 00291 reversedOrder = true; 00292 // Z-order is from left to right 00293 if ( ( angle >= 90 && angle < 180 ) || ( angle >= 270 && angle < 0 ) ) 00294 reversedOrder = false; 00295 } 00296 00297 if ( reversedOrder ) { 00298 if ( !open.hidden ) 00299 drawLine( dataset, col, leftOpenPoint, rightOpenPoint, context ); // Open marker 00300 if ( !low.hidden && !high.hidden ) 00301 drawLine( dataset, col, lowPoint, highPoint, context ); // Low-High line 00302 if ( !close.hidden ) 00303 drawLine( dataset, col, leftClosePoint, rightClosePoint, context ); // Close marker 00304 } else { 00305 if ( !close.hidden ) 00306 drawLine( dataset, col, leftClosePoint, rightClosePoint, context ); // Close marker 00307 if ( !low.hidden && !high.hidden ) 00308 drawLine( dataset, col, lowPoint, highPoint, context ); // Low-High line 00309 if ( !open.hidden ) 00310 drawLine( dataset, col, leftOpenPoint, rightOpenPoint, context ); // Open marker 00311 } 00312 00313 DataValueTextInfoList list; 00314 if ( !open.hidden ) 00315 appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( open.index ), 0, 00316 PositionPoints( leftOpenPoint ), Position::South, Position::South, open.value ); 00317 if ( !high.hidden ) 00318 appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( high.index ), 0, 00319 PositionPoints( highPoint ), Position::South, Position::South, high.value ); 00320 if ( !low.hidden ) 00321 appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( low.index ), 0, 00322 PositionPoints( lowPoint ), Position::South, Position::South, low.value ); 00323 if ( !close.hidden ) 00324 appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( close.index ), 0, 00325 PositionPoints( rightClosePoint ), Position::South, Position::South, close.value ); 00326 paintDataValueTextsAndMarkers( diagram, context, list, false ); 00327 } 00328 00336 void StockDiagram::Private::drawCandlestick( int /*dataset*/, const CartesianDiagramDataCompressor::DataPoint &open, 00337 const CartesianDiagramDataCompressor::DataPoint &high, 00338 const CartesianDiagramDataCompressor::DataPoint &low, 00339 const CartesianDiagramDataCompressor::DataPoint &close, 00340 PaintContext *context ) 00341 { 00342 PainterSaver painterSaver( context->painter() ); 00343 00344 // Note: A row in the model is a column in a StockDiagram, and the other way around 00345 const int row = low.index.row(); 00346 const int col = low.index.column(); 00347 00348 QPointF bottomCandlestickPoint; 00349 QPointF topCandlestickPoint; 00350 QBrush brush; 00351 QPen pen; 00352 bool drawLowerLine; 00353 bool drawCandlestick = !open.hidden && !close.hidden; 00354 bool drawUpperLine; 00355 00356 // Find out if we need to paint a down-trend or up-trend candlestick 00357 // and set brush and pen accordingly 00358 // Also, determine what the top and bottom points of the candlestick are 00359 if ( open.value <= close.value ) { 00360 pen = diagram->upTrendCandlestickPen( row ); 00361 brush = diagram->upTrendCandlestickBrush( row ); 00362 bottomCandlestickPoint = QPointF( open.key, open.value ); 00363 topCandlestickPoint = QPointF( close.key, close.value ); 00364 drawLowerLine = !low.hidden && !open.hidden; 00365 drawUpperLine = !low.hidden && !close.hidden; 00366 } else { 00367 pen = diagram->downTrendCandlestickPen( row ); 00368 brush = diagram->downTrendCandlestickBrush( row ); 00369 bottomCandlestickPoint = QPointF( close.key, close.value ); 00370 topCandlestickPoint = QPointF( open.key, open.value ); 00371 drawLowerLine = !low.hidden && !close.hidden; 00372 drawUpperLine = !low.hidden && !open.hidden; 00373 } 00374 00375 StockBarAttributes attr = diagram->stockBarAttributes( col ); 00376 ThreeDBarAttributes threeDAttr = diagram->threeDBarAttributes( col ); 00377 00378 const QPointF lowPoint = projectPoint( context, QPointF( low.key, low.value ) ); 00379 const QPointF highPoint = projectPoint( context, QPointF( high.key, high.value ) ); 00380 const QLineF lowerLine = QLineF( lowPoint, projectPoint( context, bottomCandlestickPoint ) ); 00381 const QLineF upperLine = QLineF( projectPoint( context, topCandlestickPoint ), highPoint ); 00382 00383 // Convert the data point into coordinates on the coordinate plane 00384 QRectF candlestick = projectCandlestick( context, bottomCandlestickPoint, 00385 topCandlestickPoint, attr.candlestickWidth() ); 00386 00387 // Remember the drawn polygon to add it to the ReverseMapper later 00388 QPolygonF drawnPolygon; 00389 00390 // Use the ThreeDPainter class to draw a 3D candlestick 00391 if ( threeDAttr.isEnabled() ) { 00392 ThreeDPainter threeDPainter( context->painter() ); 00393 00394 ThreeDPainter::ThreeDProperties threeDProps; 00395 threeDProps.depth = threeDAttr.depth(); 00396 threeDProps.angle = threeDAttr.angle(); 00397 threeDProps.useShadowColors = threeDAttr.useShadowColors(); 00398 00399 // If the perspective angle is within [0,180], we paint from bottom to top, 00400 // otherwise from top to bottom to ensure the correct z order 00401 if ( threeDProps.angle > 0.0 && threeDProps.angle < 180.0 ) { 00402 if ( drawLowerLine ) 00403 drawnPolygon = threeDPainter.drawTwoDLine( lowerLine, pen, threeDProps ); 00404 if ( drawCandlestick ) 00405 drawnPolygon = threeDPainter.drawThreeDRect( candlestick, brush, pen, threeDProps ); 00406 if ( drawUpperLine ) 00407 drawnPolygon = threeDPainter.drawTwoDLine( upperLine, pen, threeDProps ); 00408 } else { 00409 if ( drawUpperLine ) 00410 drawnPolygon = threeDPainter.drawTwoDLine( upperLine, pen, threeDProps ); 00411 if ( drawCandlestick ) 00412 drawnPolygon = threeDPainter.drawThreeDRect( candlestick, brush, pen, threeDProps ); 00413 if ( drawLowerLine ) 00414 drawnPolygon = threeDPainter.drawTwoDLine( lowerLine, pen, threeDProps ); 00415 } 00416 } else { 00417 QPainter *const painter = context->painter(); 00418 painter->setBrush( brush ); 00419 painter->setPen( pen ); 00420 if ( drawLowerLine ) 00421 painter->drawLine( lowerLine ); 00422 if ( drawUpperLine ) 00423 painter->drawLine( upperLine ); 00424 if ( drawCandlestick ) 00425 painter->drawRect( candlestick ); 00426 00427 // The 2D representation is the projected candlestick itself 00428 drawnPolygon = candlestick; 00429 00430 // FIXME: Add lower and upper line to reverse mapper 00431 } 00432 00433 DataValueTextInfoList list; 00434 00435 if ( !low.hidden ) 00436 appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( low.index ), 0, 00437 PositionPoints( lowPoint ), Position::South, Position::South, low.value ); 00438 if ( drawCandlestick ) { 00439 // Both, the open as well as the close value are represented by this candlestick 00440 reverseMapper.addPolygon( row, openValueColumn(), drawnPolygon ); 00441 reverseMapper.addPolygon( row, closeValueColumn(), drawnPolygon ); 00442 00443 appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( open.index ), 0, 00444 PositionPoints( candlestick.bottomRight() ), Position::South, Position::South, open.value ); 00445 appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( close.index ), 0, 00446 PositionPoints( candlestick.topRight() ), Position::South, Position::South, close.value ); 00447 } 00448 if ( !high.hidden ) 00449 appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( high.index ), 0, 00450 PositionPoints( highPoint ), Position::South, Position::South, high.value ); 00451 00452 paintDataValueTextsAndMarkers( diagram, context, list, false ); 00453 } 00454 00463 void StockDiagram::Private::drawLine( int dataset, int col, const QPointF &point1, const QPointF &point2, PaintContext *context ) 00464 { 00465 PainterSaver painterSaver( context->painter() ); 00466 00467 // A row in the model is a column in the diagram 00468 const int modelRow = col; 00469 const int modelCol = 0; 00470 00471 const QPen pen = diagram->pen( dataset ); 00472 const QBrush brush = diagram->brush( dataset ); 00473 const ThreeDBarAttributes threeDBarAttr = diagram->threeDBarAttributes( col ); 00474 00475 QPointF transP1 = context->coordinatePlane()->translate( point1 ); 00476 QPointF transP2 = context->coordinatePlane()->translate( point2 ); 00477 QLineF line = QLineF( transP1, transP2 ); 00478 00479 if ( threeDBarAttr.isEnabled() ) { 00480 ThreeDPainter::ThreeDProperties threeDProps; 00481 threeDProps.angle = threeDBarAttr.angle(); 00482 threeDProps.depth = threeDBarAttr.depth(); 00483 threeDProps.useShadowColors = threeDBarAttr.useShadowColors(); 00484 00485 ThreeDPainter painter( context->painter() ); 00486 reverseMapper.addPolygon( modelCol, modelRow, painter.drawThreeDLine( line, brush, pen, threeDProps ) ); 00487 } else { 00488 context->painter()->setPen( pen ); 00489 //context->painter()->setBrush( brush ); 00490 reverseMapper.addLine( modelCol, modelRow, transP1, transP2 ); 00491 context->painter()->drawLine( line ); 00492 } 00493 } 00494 00500 int StockDiagram::Private::openValueColumn() const 00501 { 00502 // Return an invalid column if diagram has no open values 00503 return type == HighLowClose ? -1 : 0; 00504 } 00505 00511 int StockDiagram::Private::highValueColumn() const 00512 { 00513 return type == HighLowClose ? 0 : 1; 00514 } 00515 00521 int StockDiagram::Private::lowValueColumn() const 00522 { 00523 return type == HighLowClose ? 1 : 2; 00524 } 00525 00531 int StockDiagram::Private::closeValueColumn() const 00532 { 00533 return type == HighLowClose ? 2 : 3; 00534 } 00535