KDChartStockDiagram_p.cpp

Go to the documentation of this file.
00001 #include "KDChartStockDiagram_p.h"
00002 
00003 using namespace KDChart;
00004 
00005 
00006 class StockDiagram::Private::ThreeDPainter
00007 {
00008 public:
00009     struct ThreeDProperties {
00010         qreal depth;
00011         qreal angle;
00012         bool useShadowColors;
00013     };
00014 
00015     ThreeDPainter( QPainter *p )
00016         : painter( p ) {};
00017 
00018     QPolygonF drawTwoDLine( const QLineF &line, const QPen &pen,
00019                             const ThreeDProperties &props );
00020     QPolygonF drawThreeDLine( const QLineF &line, const QBrush &brush,
00021                               const QPen &pen, const ThreeDProperties &props );
00022     QPolygonF drawThreeDRect( const QRectF &rect, const QBrush &brush,
00023                               const QPen &pen, const ThreeDProperties &props );
00024 
00025 private:
00026     QPointF projectPoint( const QPointF &point, qreal depth, qreal angle ) const;
00027     QColor calcShadowColor( const QColor &color, qreal angle ) const;
00028 
00029     QPainter *painter;
00030 };
00031 
00038 QPointF StockDiagram::Private::ThreeDPainter::projectPoint( const QPointF &point, qreal depth, qreal angle ) const
00039 {
00040     const qreal angleInRad = DEGTORAD( angle );
00041     const qreal distX = depth * cos( angleInRad );
00042     // Y coordinates are reversed on our coordinate plane
00043     const qreal distY = depth * -sin( angleInRad );
00044 
00045     return QPointF( point.x() + distX, point.y() + distY );
00046 }
00047 
00054 QColor StockDiagram::Private::ThreeDPainter::calcShadowColor( const QColor &color, qreal angle ) const
00055 {
00056     // The shadow factor determines to how many percent the brightness
00057     // of the color can be reduced. That is, the darkest shadow color
00058     // is color * shadowFactor.
00059     const qreal shadowFactor = 0.5;
00060     const qreal sinAngle = 1.0 - qAbs( sin( DEGTORAD( angle ) ) ) * shadowFactor;
00061     return QColor( qRound( color.red()   * sinAngle ),
00062                    qRound( color.green() * sinAngle ),
00063                    qRound( color.blue()  * sinAngle ) );
00064 }
00065 
00074 QPolygonF StockDiagram::Private::ThreeDPainter::drawTwoDLine( const QLineF &line, const QPen &pen,
00075                                                               const ThreeDProperties &props )
00076 {
00077     // Restores the painting properties when destroyed
00078     PainterSaver painterSaver( painter );
00079 
00080     // The z coordinate to use (i.e., at what depth to draw the line)
00081     const qreal z = props.depth / 2.0;
00082 
00083     // Projec the 2D points of the line in 3D
00084     const QPointF deepP1 = projectPoint( line.p1(), z, props.angle );
00085     const QPointF deepP2 = projectPoint( line.p2(), z, props.angle );
00086 
00087     // The drawn line with a width of 2px
00088     QPolygonF threeDArea;
00089     // The offset of the line "borders" from the center to each side
00090     const QPointF offset( 0.0, 1.0 );
00091     threeDArea << deepP1 - offset << deepP2 - offset
00092                << deepP1 + offset << deepP2 + offset << deepP1 - offset;
00093 
00094     painter->setPen( pen );
00095     painter->drawLine( QLineF( deepP1, deepP2 ) );
00096 
00097     return threeDArea;
00098 }
00099 
00109 QPolygonF StockDiagram::Private::ThreeDPainter::drawThreeDLine( const QLineF &line, const QBrush &brush,
00110                                                                 const QPen &pen, const ThreeDProperties &props )
00111 {
00112     // Restores the painting properties when destroyed
00113     PainterSaver painterSaver( painter );
00114 
00115     const QPointF p1 = line.p1();
00116     const QPointF p2 = line.p2();
00117 
00118     // Project the 2D points of the line in 3D
00119     const QPointF deepP1 = projectPoint( p1, props.depth, props.angle );
00120     const QPointF deepP2 = projectPoint( p2, props.depth, props.angle );
00121 
00122     // The result is a 3D representation of the 2D line
00123     QPolygonF threeDArea;
00124     threeDArea << p1 << p2 << deepP2 << deepP1 << p1;
00125 
00126     // Use shadow colors if ThreeDProperties::useShadowColors is set
00127     // Note: Setting a new color on a brush or pen does not effect gradients or textures
00128     if ( props.useShadowColors ) {
00129         QBrush shadowBrush( brush );
00130         QPen shadowPen( pen );
00131         shadowBrush.setColor( calcShadowColor( brush.color(), props.angle ) );
00132         shadowPen.setColor( calcShadowColor( pen.color(), props.angle ) );
00133         painter->setBrush( shadowBrush );
00134         painter->setPen( shadowPen );
00135     } else {
00136         painter->setBrush( brush );
00137         painter->setPen( pen );
00138     }
00139 
00140     painter->drawPolygon( threeDArea );
00141 
00142     return threeDArea;
00143 }
00144 
00154 QPolygonF StockDiagram::Private::ThreeDPainter::drawThreeDRect( const QRectF &rect, const QBrush &brush,
00155                                                                 const QPen &pen, const ThreeDProperties &props )
00156 {
00157     // Restores the painting properties when destroyed
00158     PainterSaver painterSaver( painter );
00159 
00160     // Make sure that the top really is the top
00161     const QRectF normalizedRect = rect.normalized();
00162 
00163     // Calculate all the four sides of the rectangle
00164     const QLineF topSide = QLineF( normalizedRect.topLeft(), normalizedRect.topRight() );
00165     const QLineF bottomSide = QLineF( normalizedRect.bottomLeft(), normalizedRect.bottomRight() );
00166     const QLineF leftSide = QLineF( normalizedRect.topLeft(), normalizedRect.bottomLeft() );
00167     const QLineF rightSide = QLineF( normalizedRect.topRight(), normalizedRect.bottomRight() );
00168 
00169     QPolygonF drawnPolygon;
00170 
00171     // Shorter names are easier on the eyes
00172     const qreal angle = props.angle;
00173 
00174     // Only top and right side is visible
00175     if ( angle >= 0.0 && angle < 90.0 ) {
00176         drawnPolygon = drawnPolygon.united( drawThreeDLine( topSide, brush, pen, props ) );
00177         drawnPolygon = drawnPolygon.united( drawThreeDLine( rightSide, brush, pen, props ) );
00178     // Only top and left side is visible
00179     } else if ( angle >= 90.0 && angle < 180.0 ) {
00180         drawnPolygon = drawnPolygon.united( drawThreeDLine( topSide, brush, pen, props ) );
00181         drawnPolygon = drawnPolygon.united( drawThreeDLine( leftSide, brush, pen, props ) );
00182     // Only bottom and left side is visible
00183     } else if ( angle >= 180.0 && angle < 270.0 ) {
00184         drawnPolygon = drawnPolygon.united( drawThreeDLine( bottomSide, brush, pen, props ) );
00185         drawnPolygon = drawnPolygon.united( drawThreeDLine( leftSide, brush, pen, props ) );
00186     // Only bottom and right side is visible
00187     } else if ( angle >= 270.0 && angle <= 360.0 ) {
00188         drawnPolygon = drawnPolygon.united( drawThreeDLine( bottomSide, brush, pen, props ) );
00189         drawnPolygon = drawnPolygon.united( drawThreeDLine( rightSide, brush, pen, props ) );
00190     }
00191 
00192     // Draw the front side
00193     painter->setPen( pen );
00194     painter->setBrush( brush );
00195     painter->drawRect( normalizedRect );
00196 
00197     return drawnPolygon;
00198 }
00199 
00200 
00201 StockDiagram::Private::Private()
00202     : AbstractCartesianDiagram::Private()
00203 {
00204 }
00205 
00206 StockDiagram::Private::Private( const Private& r )
00207     : AbstractCartesianDiagram::Private( r )
00208 {
00209 }
00210 
00211 StockDiagram::Private::~Private()
00212 {
00213 }
00214 
00222 QPointF StockDiagram::Private::projectPoint( PaintContext *context, const QPointF &point ) const
00223 {
00224     return context->coordinatePlane()->translate( QPointF( point.x() + 0.5, point.y() ) );
00225 }
00226 
00233 QRectF StockDiagram::Private::projectCandlestick( PaintContext *context, const QPointF &open, const QPointF &close, qreal width ) const
00234 {
00235     const QPointF leftHighPoint = context->coordinatePlane()->translate( QPointF( close.x() + 0.5 - width / 2.0, close.y() ) );
00236     const QPointF rightLowPoint = context->coordinatePlane()->translate( QPointF( open.x() + 0.5 + width / 2.0, open.y() ) );
00237     const QPointF rightHighPoint = context->coordinatePlane()->translate( QPointF( close.x() + 0.5 + width / 2.0, close.y() ) );
00238 
00239     return QRectF( leftHighPoint, QSizeF( rightHighPoint.x() - leftHighPoint.x(),
00240                                           rightLowPoint.y() - leftHighPoint.y() ) );
00241 }
00242 
00243 void StockDiagram::Private::drawOHLCBar( const CartesianDiagramDataCompressor::DataPoint &open,
00244         const CartesianDiagramDataCompressor::DataPoint &high,
00245         const CartesianDiagramDataCompressor::DataPoint &low,
00246         const CartesianDiagramDataCompressor::DataPoint &close,
00247         PaintContext *context )
00248 {
00249     // Note: A row in the model is a column in a StockDiagram
00250     const int col = low.index.row();
00251 
00252     StockBarAttributes attr = diagram->stockBarAttributes( col );
00253     ThreeDBarAttributes threeDAttr = diagram->threeDBarAttributes( col );
00254     const qreal tickLength = attr.tickLength();
00255 
00256     const QPointF leftOpenPoint( open.key + 0.5 - tickLength, open.value );
00257     const QPointF rightOpenPoint( open.key + 0.5, open.value );
00258     const QPointF highPoint( high.key + 0.5, high.value );
00259     const QPointF lowPoint( low.key + 0.5, low.value );
00260     const QPointF leftClosePoint( close.key + 0.5, close.value );
00261     const QPointF rightClosePoint( close.key + 0.5 + tickLength, close.value );
00262 
00263     bool reversedOrder = false;
00264     // If 3D mode is enabled, we have to make sure the z-order is right
00265     if ( threeDAttr.isEnabled() ) {
00266         const int angle = threeDAttr.angle();
00267         // Z-order is from right to left
00268         if ( angle >= 0 && angle < 90 || angle >= 180 && angle < 270 )
00269             reversedOrder = true;
00270         // Z-order is from left to right
00271         if ( angle >= 90 && angle < 180 || angle >= 270 && angle < 0 )
00272             reversedOrder = false;
00273     }
00274 
00275     if ( reversedOrder ) {
00276         if ( !open.hidden )
00277             drawLine( col, leftOpenPoint, rightOpenPoint, context ); // Open marker
00278         if ( !low.hidden && !high.hidden )
00279             drawLine( col, lowPoint, highPoint, context ); // Low-High line
00280         if ( !close.hidden )
00281             drawLine( col, leftClosePoint, rightClosePoint, context ); // Close marker
00282     } else {
00283         if ( !close.hidden )
00284             drawLine( col, leftClosePoint, rightClosePoint, context ); // Close marker
00285         if ( !low.hidden && !high.hidden )
00286             drawLine( col, lowPoint, highPoint, context ); // Low-High line
00287         if ( !open.hidden )
00288             drawLine( col, leftOpenPoint, rightOpenPoint, context ); // Open marker
00289     }
00290 
00291     DataValueTextInfoList list;
00292     if ( !open.hidden )
00293         appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( open.index ), 0,
00294                                        PositionPoints( leftOpenPoint ), Position::South, Position::South, open.value );
00295     if ( !high.hidden )
00296         appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( high.index ), 0,
00297                                        PositionPoints( highPoint ), Position::South, Position::South, high.value );
00298     if ( !low.hidden )
00299         appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( low.index ), 0,
00300                                        PositionPoints( lowPoint ), Position::South, Position::South, low.value );
00301     if ( !close.hidden )
00302         appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( close.index ), 0,
00303                                        PositionPoints( rightClosePoint ), Position::South, Position::South, close.value );
00304     paintDataValueTextsAndMarkers( diagram, context, list, false );
00305 }
00306 
00314 void StockDiagram::Private::drawCandlestick( const CartesianDiagramDataCompressor::DataPoint &open,
00315                                              const CartesianDiagramDataCompressor::DataPoint &high,
00316                                              const CartesianDiagramDataCompressor::DataPoint &low,
00317                                              const CartesianDiagramDataCompressor::DataPoint &close,
00318                                              PaintContext *context )
00319 {
00320     PainterSaver painterSaver( context->painter() );
00321 
00322     // Note: A row in the model is a column in a StockDiagram, and the other way around
00323     const int row = low.index.row();
00324     const int col = low.index.column();
00325 
00326     QPointF bottomCandlestickPoint;
00327     QPointF topCandlestickPoint;
00328     QBrush brush;
00329     QPen pen;
00330     bool drawLowerLine;
00331     bool drawCandlestick = !open.hidden && !close.hidden;
00332     bool drawUpperLine;
00333 
00334     // Find out if we need to paint a down-trend or up-trend candlestick
00335     // and set brush and pen accordingly
00336     // Also, determine what the top and bottom points of the candlestick are
00337     if ( open.value <= close.value ) {
00338         pen = diagram->upTrendCandlestickPen( row );
00339         brush = diagram->upTrendCandlestickBrush( row );
00340         bottomCandlestickPoint = QPointF( open.key, open.value );
00341         topCandlestickPoint = QPointF( close.key, close.value );
00342         drawLowerLine = !low.hidden && !open.hidden;
00343         drawUpperLine = !low.hidden && !close.hidden;
00344     } else {
00345         pen = diagram->downTrendCandlestickPen( row );
00346         brush = diagram->downTrendCandlestickBrush( row );
00347         bottomCandlestickPoint = QPointF( close.key, close.value );
00348         topCandlestickPoint = QPointF( open.key, open.value );
00349         drawLowerLine = !low.hidden && !close.hidden;
00350         drawUpperLine = !low.hidden && !open.hidden;
00351     }
00352 
00353     StockBarAttributes attr = diagram->stockBarAttributes( col );
00354     ThreeDBarAttributes threeDAttr = diagram->threeDBarAttributes( col );
00355 
00356     const QPointF lowPoint = projectPoint( context, QPointF( low.key, low.value ) );
00357     const QPointF highPoint = projectPoint( context, QPointF( high.key, high.value ) );
00358     const QLineF lowerLine = QLineF( lowPoint, projectPoint( context, bottomCandlestickPoint ) );
00359     const QLineF upperLine = QLineF( projectPoint( context, topCandlestickPoint ), highPoint );
00360 
00361     // Convert the data point into coordinates on the coordinate plane
00362     QRectF candlestick = projectCandlestick( context, bottomCandlestickPoint,
00363                                              topCandlestickPoint, attr.candlestickWidth() );
00364 
00365     // Remember the drawn polygon to add it to the ReverseMapper later
00366     QPolygonF drawnPolygon;
00367 
00368     // Use the ThreeDPainter class to draw a 3D candlestick
00369     if ( threeDAttr.isEnabled() ) {
00370         ThreeDPainter threeDPainter( context->painter() );
00371 
00372         ThreeDPainter::ThreeDProperties threeDProps;
00373         threeDProps.depth = threeDAttr.depth();
00374         threeDProps.angle = threeDAttr.angle();
00375         threeDProps.useShadowColors = threeDAttr.useShadowColors();
00376 
00377         // If the perspective angle is within [0,180], we paint from bottom to top,
00378         // otherwise from top to bottom to ensure the correct z order
00379         if ( threeDProps.angle > 0.0 && threeDProps.angle < 180.0 ) {
00380             if ( drawLowerLine )
00381                 drawnPolygon = threeDPainter.drawTwoDLine( lowerLine, pen, threeDProps );
00382             if ( drawCandlestick )
00383                 drawnPolygon = threeDPainter.drawThreeDRect( candlestick, brush, pen, threeDProps );
00384             if ( drawUpperLine )
00385             drawnPolygon = threeDPainter.drawTwoDLine( upperLine, pen, threeDProps );
00386         } else {
00387             if ( drawUpperLine )
00388                 drawnPolygon = threeDPainter.drawTwoDLine( upperLine, pen, threeDProps );
00389             if ( drawCandlestick )
00390                 drawnPolygon = threeDPainter.drawThreeDRect( candlestick, brush, pen, threeDProps );
00391             if ( drawLowerLine )
00392                 drawnPolygon = threeDPainter.drawTwoDLine( lowerLine, pen, threeDProps );
00393         }
00394     } else {
00395         QPainter *const painter = context->painter();
00396         painter->setBrush( brush );
00397         painter->setPen( pen );
00398         if ( drawLowerLine )
00399             painter->drawLine( lowerLine );
00400         if ( drawUpperLine )
00401             painter->drawLine( upperLine );
00402         if ( drawCandlestick )
00403             painter->drawRect( candlestick );
00404 
00405         // The 2D representation is the projected candlestick itself
00406         drawnPolygon = candlestick;
00407 
00408         // FIXME: Add lower and upper line to reverse mapper
00409     }
00410 
00411     DataValueTextInfoList list;
00412 
00413     if ( !low.hidden )
00414         appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( low.index ), 0,
00415                                        PositionPoints( lowPoint ), Position::South, Position::South, low.value );
00416     if ( drawCandlestick ) {
00417         // Both, the open as well as the close value are represented by this candlestick
00418         reverseMapper.addPolygon( row, openValueColumn(), drawnPolygon );
00419         reverseMapper.addPolygon( row, closeValueColumn(), drawnPolygon );
00420 
00421         appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( open.index ), 0,
00422                                        PositionPoints( candlestick.bottomRight() ), Position::South, Position::South, open.value );
00423         appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( close.index ), 0,
00424                                        PositionPoints( candlestick.topRight() ), Position::South, Position::South, close.value );
00425     }
00426     if ( !high.hidden )
00427         appendDataValueTextInfoToList( diagram, list, diagram->attributesModel()->mapToSource( high.index ), 0,
00428                                        PositionPoints( highPoint ), Position::South, Position::South, high.value );
00429 
00430     paintDataValueTextsAndMarkers( diagram, context, list, false );
00431 }
00432 
00441 void StockDiagram::Private::drawLine( int col, const QPointF &point1, const QPointF &point2, PaintContext *context )
00442 {
00443     PainterSaver painterSaver( context->painter() );
00444 
00445     // A row in the model is a column in the diagram
00446     const int modelRow = col;
00447     const int modelCol = 0;
00448 
00449     const QPen pen = diagram->pen( col );
00450     const QBrush brush = diagram->brush( col );
00451     const ThreeDBarAttributes threeDBarAttr = diagram->threeDBarAttributes( col );
00452 
00453     QPointF transP1 = context->coordinatePlane()->translate( point1 );
00454     QPointF transP2 = context->coordinatePlane()->translate( point2 );
00455     QLineF line = QLineF( transP1, transP2 );
00456 
00457     if ( threeDBarAttr.isEnabled() ) {
00458         ThreeDPainter::ThreeDProperties threeDProps;
00459         threeDProps.angle = threeDBarAttr.angle();
00460         threeDProps.depth = threeDBarAttr.depth();
00461         threeDProps.useShadowColors = threeDBarAttr.useShadowColors();
00462 
00463         ThreeDPainter painter( context->painter() );
00464         reverseMapper.addPolygon( modelCol, modelRow, painter.drawThreeDLine( line, brush, pen, threeDProps ) );
00465     } else {
00466         context->painter()->setPen( pen );
00467         //context->painter()->setBrush( brush );
00468         reverseMapper.addLine( modelCol, modelRow, transP1, transP2 );
00469         context->painter()->drawLine( line );
00470     }
00471 }
00472 
00478 int StockDiagram::Private::openValueColumn() const
00479 {
00480     // Return an invalid column if diagram has no open values
00481     return type == HighLowClose ? -1 : 0;
00482 }
00483 
00489 int StockDiagram::Private::highValueColumn() const
00490 {
00491     return type == HighLowClose ? 0 : 1;
00492 }
00493 
00499 int StockDiagram::Private::lowValueColumn() const
00500 {
00501     return type == HighLowClose ? 1 : 2;
00502 }
00503 
00509 int StockDiagram::Private::closeValueColumn() const
00510 {
00511     return type == HighLowClose ? 2 : 3;
00512 }
00513 

Generated on Thu Mar 4 23:19:12 2010 for KD Chart 2 by  doxygen 1.5.4