1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtCharts/QCandlestickSet>
5#include <QtGui/QPainter>
6#include <private/abstractdomain_p.h>
7#include <private/candlestick_p.h>
8#include <private/qchart_p.h>
9
10QT_BEGIN_NAMESPACE
11
12Candlestick::Candlestick(QCandlestickSet *set, AbstractDomain *domain, QGraphicsObject *parent)
13 : QGraphicsObject(parent),
14 m_set(set),
15 m_domain(domain),
16 m_timePeriod(0.0),
17 m_maximumColumnWidth(-1.0), // no maximum column width by default
18 m_minimumColumnWidth(-1.0), // no minimum column width by default
19 m_bodyWidth(0.5),
20 m_bodyOutlineVisible(true),
21 m_capsWidth(0.5),
22 m_capsVisible(false),
23 m_brush(QChartPrivate::defaultBrush()),
24 m_pen(QChartPrivate::defaultPen()),
25 m_hovering(false),
26 m_mousePressed(false)
27{
28 setAcceptHoverEvents(true);
29 setAcceptedMouseButtons(Qt::MouseButtonMask);
30 setFlag(flag: QGraphicsObject::ItemIsSelectable);
31}
32
33Candlestick::~Candlestick()
34{
35 // End hover event, if candlestick is deleted during it.
36 if (m_hovering)
37 emit hovered(status: false, set: m_set);
38}
39
40void Candlestick::setTimePeriod(qreal timePeriod)
41{
42 m_timePeriod = timePeriod;
43}
44
45void Candlestick::setMaximumColumnWidth(qreal maximumColumnWidth)
46{
47 m_maximumColumnWidth = maximumColumnWidth;
48}
49
50void Candlestick::setMinimumColumnWidth(qreal minimumColumnWidth)
51{
52 m_minimumColumnWidth = minimumColumnWidth;
53}
54
55void Candlestick::setBodyWidth(qreal bodyWidth)
56{
57 m_bodyWidth = bodyWidth;
58}
59
60void Candlestick::setBodyOutlineVisible(bool bodyOutlineVisible)
61{
62 m_bodyOutlineVisible = bodyOutlineVisible;
63}
64
65void Candlestick::setCapsWidth(qreal capsWidth)
66{
67 m_capsWidth = capsWidth;
68}
69
70void Candlestick::setCapsVisible(bool capsVisible)
71{
72 m_capsVisible = capsVisible;
73}
74
75void Candlestick::setIncreasingColor(const QColor &color)
76{
77 m_increasingColor = color;
78
79 update();
80}
81
82void Candlestick::setDecreasingColor(const QColor &color)
83{
84 m_decreasingColor = color;
85
86 update();
87}
88
89void Candlestick::setBrush(const QBrush &brush)
90{
91 m_brush = brush;
92
93 update();
94}
95
96void Candlestick::setPen(const QPen &pen)
97{
98 qreal widthDiff = pen.widthF() - m_pen.widthF();
99 m_boundingRect.adjust(xp1: -widthDiff, yp1: -widthDiff, xp2: widthDiff, yp2: widthDiff);
100
101 m_pen = pen;
102
103 update();
104}
105
106void Candlestick::setLayout(const CandlestickData &data)
107{
108 m_data = data;
109
110 updateGeometry(domain: m_domain);
111 update();
112}
113
114void Candlestick::mousePressEvent(QGraphicsSceneMouseEvent *event)
115{
116 m_mousePressed = true;
117 emit pressed(set: m_set);
118 QGraphicsItem::mousePressEvent(event);
119}
120
121void Candlestick::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
122{
123 Q_UNUSED(event);
124
125 m_hovering = true;
126 emit hovered(status: m_hovering, set: m_set);
127}
128
129void Candlestick::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
130{
131 Q_UNUSED(event);
132
133 m_hovering = false;
134 emit hovered(status: m_hovering, set: m_set);
135}
136
137void Candlestick::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
138{
139 emit released(set: m_set);
140 if (m_mousePressed)
141 emit clicked(set: m_set);
142 m_mousePressed = false;
143 QGraphicsItem::mouseReleaseEvent(event);
144}
145
146void Candlestick::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
147{
148 // For candlestick a pressed signal needs to be explicitly fired for mouseDoubleClickEvent.
149 emit pressed(set: m_set);
150 emit doubleClicked(set: m_set);
151 QGraphicsItem::mouseDoubleClickEvent(event);
152}
153
154QRectF Candlestick::boundingRect() const
155{
156 return m_boundingRect;
157}
158
159void Candlestick::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
160{
161 Q_UNUSED(option);
162 Q_UNUSED(widget);
163
164 bool increasingTrend = (m_data.m_open < m_data.m_close);
165 QColor color = increasingTrend ? m_increasingColor : m_decreasingColor;
166
167 QBrush brush(m_brush);
168 brush.setColor(color);
169
170 painter->save();
171 painter->setBrush(brush);
172 painter->setPen(m_pen);
173 painter->setClipRect(m_boundingRect);
174 if (m_capsVisible)
175 painter->drawPath(path: m_capsPath);
176 painter->drawPath(path: m_wicksPath);
177 if (!m_bodyOutlineVisible)
178 painter->setPen(QColor(Qt::transparent));
179 painter->drawRect(rect: m_bodyRect);
180 painter->restore();
181}
182
183void Candlestick::updateGeometry(AbstractDomain *domain)
184{
185 m_domain = domain;
186
187 prepareGeometryChange();
188
189 m_capsPath = QPainterPath();
190 m_wicksPath = QPainterPath();
191 m_boundingRect = QRectF();
192
193 if (!m_data.m_series->chart())
194 return;
195
196 QList<QAbstractAxis *> axes = m_data.m_series->chart()->axes(orientation: Qt::Horizontal, series: m_data.m_series);
197 if (axes.isEmpty())
198 return;
199
200 QAbstractAxis *axisX = axes.value(i: 0);
201 if (!axisX)
202 return;
203
204 qreal columnWidth = 0.0;
205 qreal columnCenter = 0.0;
206 switch (axisX->type()) {
207 case QAbstractAxis::AxisTypeBarCategory:
208 columnWidth = 1.0 / m_data.m_seriesCount;
209 columnCenter = m_data.m_index - 0.5
210 + m_data.m_seriesIndex * columnWidth
211 + columnWidth / 2.0;
212 break;
213 case QAbstractAxis::AxisTypeDateTime:
214 case QAbstractAxis::AxisTypeValue:
215 columnWidth = m_timePeriod;
216 columnCenter = m_data.m_timestamp;
217 break;
218 default:
219 qWarning() << "Unexpected axis type";
220 return;
221 }
222
223 const qreal bodyWidth = m_bodyWidth * columnWidth;
224 const qreal bodyLeft = columnCenter - (bodyWidth / 2.0);
225 const qreal bodyRight = bodyLeft + bodyWidth;
226
227 const qreal upperBody = qMax(a: m_data.m_open, b: m_data.m_close);
228 const qreal lowerBody = qMin(a: m_data.m_open, b: m_data.m_close);
229 const bool upperWickVisible = (m_data.m_high > upperBody);
230 const bool lowerWickVisible = (m_data.m_low < lowerBody);
231
232 QPointF geometryPoint;
233 bool validData;
234
235 // upper extreme
236 geometryPoint = m_domain->calculateGeometryPoint(point: QPointF(bodyLeft, m_data.m_high), ok&: validData);
237 if (!validData)
238 return;
239 const qreal geometryUpperExtreme = geometryPoint.y();
240 // upper body
241 geometryPoint = m_domain->calculateGeometryPoint(point: QPointF(bodyLeft, upperBody), ok&: validData);
242 if (!validData)
243 return;
244 const qreal geometryBodyLeft = geometryPoint.x();
245 const qreal geometryUpperBody = geometryPoint.y();
246 // lower body
247 geometryPoint = m_domain->calculateGeometryPoint(point: QPointF(bodyRight, lowerBody), ok&: validData);
248 if (!validData)
249 return;
250 const qreal geometryBodyRight = geometryPoint.x();
251 const qreal geometryLowerBody = geometryPoint.y();
252 // lower extreme
253 geometryPoint = m_domain->calculateGeometryPoint(point: QPointF(bodyRight, m_data.m_low), ok&: validData);
254 if (!validData)
255 return;
256 const qreal geometryLowerExtreme = geometryPoint.y();
257
258 // Real Body
259 m_bodyRect.setCoords(xp1: geometryBodyLeft, yp1: geometryUpperBody, xp2: geometryBodyRight, yp2: geometryLowerBody);
260 if (m_maximumColumnWidth != -1.0) {
261 if (m_bodyRect.width() > m_maximumColumnWidth) {
262 qreal extra = (m_bodyRect.width() - m_maximumColumnWidth) / 2.0;
263 m_bodyRect.adjust(xp1: extra, yp1: 0.0, xp2: 0.0, yp2: 0.0);
264 m_bodyRect.setWidth(m_maximumColumnWidth);
265 }
266 }
267 if (m_minimumColumnWidth != -1.0) {
268 if (m_bodyRect.width() < m_minimumColumnWidth) {
269 qreal extra = (m_minimumColumnWidth - m_bodyRect.width()) / 2.0;
270 m_bodyRect.adjust(xp1: -extra, yp1: 0.0, xp2: 0.0, yp2: 0.0);
271 m_bodyRect.setWidth(m_minimumColumnWidth);
272 }
273 }
274
275 const qreal geometryCapsExtra = (m_bodyRect.width() - (m_bodyRect.width() * m_capsWidth)) /2.0;
276 const qreal geometryCapsLeft = m_bodyRect.left() + geometryCapsExtra;
277 const qreal geometryCapsRight = m_bodyRect.right() - geometryCapsExtra;
278
279 // Upper Wick and Cap
280 if (upperWickVisible) {
281 m_capsPath.moveTo(x: geometryCapsLeft, y: geometryUpperExtreme);
282 m_capsPath.lineTo(x: geometryCapsRight, y: geometryUpperExtreme);
283 m_wicksPath.moveTo(x: (geometryCapsLeft + geometryCapsRight) / 2.0, y: geometryUpperExtreme);
284 m_wicksPath.lineTo(x: (geometryCapsLeft + geometryCapsRight) / 2.0, y: geometryUpperBody);
285 }
286 // Lower Wick and Cap
287 if (lowerWickVisible) {
288 m_capsPath.moveTo(x: geometryCapsLeft, y: geometryLowerExtreme);
289 m_capsPath.lineTo(x: geometryCapsRight, y: geometryLowerExtreme);
290 m_wicksPath.moveTo(x: (geometryCapsLeft + geometryCapsRight) / 2.0, y: geometryLowerBody);
291 m_wicksPath.lineTo(x: (geometryCapsLeft + geometryCapsRight) / 2.0, y: geometryLowerExtreme);
292 }
293 m_wicksPath.closeSubpath();
294
295 // bounding rectangle top
296 qreal boundingRectTop;
297 if (upperWickVisible)
298 boundingRectTop = m_wicksPath.boundingRect().top();
299 else
300 boundingRectTop = m_bodyRect.top();
301 boundingRectTop = qMax(a: boundingRectTop, b: parentItem()->boundingRect().top());
302 // bounding rectangle right
303 qreal boundingRectRight = qMin(a: m_bodyRect.right(), b: parentItem()->boundingRect().right());
304 // bounding rectangle bottom
305 qreal boundingRectBottom;
306 if (lowerWickVisible)
307 boundingRectBottom = m_wicksPath.boundingRect().bottom();
308 else
309 boundingRectBottom = m_bodyRect.bottom();
310 boundingRectBottom = qMin(a: boundingRectBottom, b: parentItem()->boundingRect().bottom());
311 // bounding rectangle left
312 qreal boundingRectLeft = qMax(a: m_bodyRect.left(), b: parentItem()->boundingRect().left());
313
314 m_boundingRect.setTop(boundingRectTop);
315 m_boundingRect.setRight(boundingRectRight);
316 m_boundingRect.setBottom(boundingRectBottom);
317 m_boundingRect.setLeft(boundingRectLeft);
318
319 qreal extra = m_pen.widthF();
320 m_boundingRect.adjust(xp1: -extra, yp1: -extra, xp2: extra, yp2: extra);
321}
322
323QT_END_NAMESPACE
324
325#include "moc_candlestick_p.cpp"
326

source code of qtcharts/src/charts/candlestickchart/candlestick.cpp