diff --git a/examples/Stock/Advanced/OHLC.csv b/examples/Stock/Advanced/OHLC.csv index 3f741c7b..014716ba 100644 --- a/examples/Stock/Advanced/OHLC.csv +++ b/examples/Stock/Advanced/OHLC.csv @@ -1,8 +1,8 @@ ,"Foobar Inc." -January, 20.3, 42.4, 10.3, 32.0 -February, 32.0, 37.3, 20.0, 25.9 -March, 25.9, 33.7, 20.3, 29.0 -April, 29.9, 53.3, 28.1, 39.0 -May, 39.0, 45.0, 36.4, 41.9 -June, 41.9, 63.3, 41.0, 58.7 -July, 58.7, 68.5, 50.7, 55.3 +January, 20.3, 42.4, 10.3, 32.0, 23 +February, 32.0, 37.3, 20.0, 25.9, 28.4 +March, 25.9, 33.7, 20.3, 29.0, 26.5 +April, 29.9, 53.3, 28.1, 39.0, 35 +May, 39.0, 45.0, 36.4, 41.9, 40 +June, 41.9, 63.3, 41.0, 58.7, 55 +July, 58.7, 68.5, 50.7, 55.3, 56 diff --git a/examples/Stock/Advanced/mainwindow.cpp b/examples/Stock/Advanced/mainwindow.cpp index e7548b78..7845601c 100644 --- a/examples/Stock/Advanced/mainwindow.cpp +++ b/examples/Stock/Advanced/mainwindow.cpp @@ -72,6 +72,7 @@ void MainWindow::applyColor(const QColor &color) QColor inverse(255 - color.red(), 255 - color.green(), 255 - color.blue()); m_diagram.setPen(1, QPen(inverse.darker(130))); m_diagram.setBrush(1, QBrush(inverse)); + QPalette pal = colorChooser->palette(); pal.setBrush(QPalette::Button, QBrush(color)); colorChooser->setPalette(pal); @@ -133,6 +134,7 @@ void MainWindow::on_stockTypeCB_currentIndexChanged(int index) } else if (text == "Candlestick") { m_diagram.setType(StockDiagram::Candlestick); m_diagram.setModel(&m_OHLCModel); + m_diagram.setDataContainsMedianValues(true); } m_chart->coordinatePlane()->replaceDiagram(&m_diagram); diff --git a/src/KDChart/Cartesian/KDChartStockDiagram.cpp b/src/KDChart/Cartesian/KDChartStockDiagram.cpp index f9b0e8a0..32b3340b 100644 --- a/src/KDChart/Cartesian/KDChartStockDiagram.cpp +++ b/src/KDChart/Cartesian/KDChartStockDiagram.cpp @@ -286,7 +286,18 @@ void StockDiagram::paint(PaintContext *context) PainterSaver painterSaver(context->painter()); const int rowCount = attributesModel()->rowCount(attributesModelRootIndex()); - const int divisor = (d->type == OpenHighLowClose || d->type == Candlestick) ? 4 : 3; + const int divisor = [&] { + switch (d->type) { + case HighLowClose: + return 3; + case OpenHighLowClose: + return 4; + case Candlestick: + return d->dataContainsMedian ? 5 : 4; + } + Q_UNREACHABLE(); + }(); + const int colCount = attributesModel()->columnCount(attributesModelRootIndex()) / divisor; for (int col = 0; col < colCount; ++col) { for (int row = 0; row < rowCount; row++) { @@ -295,6 +306,8 @@ void StockDiagram::paint(PaintContext *context) CartesianDiagramDataCompressor::DataPoint open; CartesianDiagramDataCompressor::DataPoint close; CartesianDiagramDataCompressor::DataPoint volume; + CartesianDiagramDataCompressor::DataPoint median; + if (d->type == HighLowClose) { const CartesianDiagramDataCompressor::CachePosition highPos(row, col * divisor); @@ -312,6 +325,10 @@ void StockDiagram::paint(PaintContext *context) low = d->compressor.data(lowPos); high = d->compressor.data(highPos); close = d->compressor.data(closePos); + if (d->type == Candlestick) { + const CartesianDiagramDataCompressor::CachePosition medianPos(row, col * divisor + 4); + median = d->compressor.data(medianPos); + } } switch (d->type) { @@ -323,7 +340,8 @@ void StockDiagram::paint(PaintContext *context) d->drawOHLCBar(col, open, high, low, close, context); break; case Candlestick: - d->drawCandlestick(col, open, high, low, close, context); + median.hidden = !d->dataContainsMedian; + d->drawCandlestick(col, open, high, low, close, median, context); break; } } @@ -352,6 +370,11 @@ qreal StockDiagram::threeDItemDepth(const QModelIndex &index) const return 1.0; } +void StockDiagram::setDataContainsMedianValues(bool b) +{ + d->dataContainsMedian = b; +} + const QPair StockDiagram::calculateDataBoundaries() const { const int rowCount = attributesModel()->rowCount(attributesModelRootIndex()); diff --git a/src/KDChart/Cartesian/KDChartStockDiagram.h b/src/KDChart/Cartesian/KDChartStockDiagram.h index 1deb2c55..e7160249 100644 --- a/src/KDChart/Cartesian/KDChartStockDiagram.h +++ b/src/KDChart/Cartesian/KDChartStockDiagram.h @@ -98,6 +98,8 @@ class KDCHART_EXPORT StockDiagram : public AbstractCartesianDiagram qreal threeDItemDepth(int column) const override; qreal threeDItemDepth(const QModelIndex &index) const override; + void setDataContainsMedianValues(bool b = true); + protected: const QPair calculateDataBoundaries() const override; }; diff --git a/src/KDChart/Cartesian/KDChartStockDiagram_p.cpp b/src/KDChart/Cartesian/KDChartStockDiagram_p.cpp index 02820be0..25302f35 100644 --- a/src/KDChart/Cartesian/KDChartStockDiagram_p.cpp +++ b/src/KDChart/Cartesian/KDChartStockDiagram_p.cpp @@ -328,6 +328,7 @@ void StockDiagram::Private::drawCandlestick(int /*dataset*/, const CartesianDiag const CartesianDiagramDataCompressor::DataPoint &high, const CartesianDiagramDataCompressor::DataPoint &low, const CartesianDiagramDataCompressor::DataPoint &close, + const CartesianDiagramDataCompressor::DataPoint &median, PaintContext *context) { PainterSaver painterSaver(context->painter()); @@ -342,6 +343,7 @@ void StockDiagram::Private::drawCandlestick(int /*dataset*/, const CartesianDiag QPen pen; bool drawLowerLine; bool drawCandlestick = !open.hidden && !close.hidden; + bool drawMedian = !median.hidden; bool drawUpperLine; // Find out if we need to paint a down-trend or up-trend candlestick @@ -363,6 +365,19 @@ void StockDiagram::Private::drawCandlestick(int /*dataset*/, const CartesianDiag drawUpperLine = !low.hidden && !open.hidden; } + auto medianColor = [&] { + auto brushColor = brush.color(); + if (brushColor.isValid()) { + return (0.21 * brushColor.red() + 0.72 * brushColor.green() + 0.07 * brushColor.blue()) < 127 + ? QColor(Qt::white) + : QColor(Qt::black); + } else { + // Fallback to pen color, we could do better by letting the caller provide the pen, but I'm out of attention span for that. + return pen.color(); + } + }; + + StockBarAttributes attr = stockDiagram()->stockBarAttributes(col); ThreeDBarAttributes threeDAttr = stockDiagram()->threeDBarAttributes(col); @@ -375,6 +390,11 @@ void StockDiagram::Private::drawCandlestick(int /*dataset*/, const CartesianDiag QRectF candlestick = projectCandlestick(context, bottomCandlestickPoint, topCandlestickPoint, attr.candlestickWidth()); + const auto medianLine = [&] { + const QPointF medianPoint = projectPoint(context, QPointF(median.key, median.value)); + return QLineF(candlestick.left(), medianPoint.y(), candlestick.right(), medianPoint.y()); + }; + // Remember the drawn polygon to add it to the ReverseMapper later QPolygonF drawnPolygon; @@ -396,6 +416,7 @@ void StockDiagram::Private::drawCandlestick(int /*dataset*/, const CartesianDiag drawnPolygon = threeDPainter.drawThreeDRect(candlestick, brush, pen, threeDProps); if (drawUpperLine) drawnPolygon = threeDPainter.drawTwoDLine(upperLine, pen, threeDProps); + // TODO: Draw median in 3D } else { if (drawUpperLine) drawnPolygon = threeDPainter.drawTwoDLine(upperLine, pen, threeDProps); @@ -403,6 +424,7 @@ void StockDiagram::Private::drawCandlestick(int /*dataset*/, const CartesianDiag drawnPolygon = threeDPainter.drawThreeDRect(candlestick, brush, pen, threeDProps); if (drawLowerLine) drawnPolygon = threeDPainter.drawTwoDLine(lowerLine, pen, threeDProps); + // TODO: Draw median in 3D } } else { QPainter *const painter = context->painter(); @@ -414,6 +436,11 @@ void StockDiagram::Private::drawCandlestick(int /*dataset*/, const CartesianDiag painter->drawLine(upperLine); if (drawCandlestick) painter->drawRect(candlestick); + if (drawMedian) { + painter->setPen(medianColor()); + painter->drawLine(medianLine()); + painter->setPen(pen); + } // The 2D representation is the projected candlestick itself drawnPolygon = candlestick; diff --git a/src/KDChart/Cartesian/KDChartStockDiagram_p.h b/src/KDChart/Cartesian/KDChartStockDiagram_p.h index c9b8613a..aa795c4d 100644 --- a/src/KDChart/Cartesian/KDChartStockDiagram_p.h +++ b/src/KDChart/Cartesian/KDChartStockDiagram_p.h @@ -45,6 +45,7 @@ class StockDiagram::Private : public AbstractCartesianDiagram::Private QPen lowHighLinePen; QMap lowHighLinePens; + bool dataContainsMedian = false; void drawOHLCBar(int dataset, const CartesianDiagramDataCompressor::DataPoint &open, const CartesianDiagramDataCompressor::DataPoint &high, @@ -59,6 +60,7 @@ class StockDiagram::Private : public AbstractCartesianDiagram::Private const CartesianDiagramDataCompressor::DataPoint &high, const CartesianDiagramDataCompressor::DataPoint &low, const CartesianDiagramDataCompressor::DataPoint &close, + const CartesianDiagramDataCompressor::DataPoint &median, PaintContext *context); private: