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 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | Candlestick::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 | |
33 | Candlestick::~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 | |
40 | void Candlestick::setTimePeriod(qreal timePeriod) |
41 | { |
42 | m_timePeriod = timePeriod; |
43 | } |
44 | |
45 | void Candlestick::setMaximumColumnWidth(qreal maximumColumnWidth) |
46 | { |
47 | m_maximumColumnWidth = maximumColumnWidth; |
48 | } |
49 | |
50 | void Candlestick::setMinimumColumnWidth(qreal minimumColumnWidth) |
51 | { |
52 | m_minimumColumnWidth = minimumColumnWidth; |
53 | } |
54 | |
55 | void Candlestick::setBodyWidth(qreal bodyWidth) |
56 | { |
57 | m_bodyWidth = bodyWidth; |
58 | } |
59 | |
60 | void Candlestick::setBodyOutlineVisible(bool bodyOutlineVisible) |
61 | { |
62 | m_bodyOutlineVisible = bodyOutlineVisible; |
63 | } |
64 | |
65 | void Candlestick::setCapsWidth(qreal capsWidth) |
66 | { |
67 | m_capsWidth = capsWidth; |
68 | } |
69 | |
70 | void Candlestick::setCapsVisible(bool capsVisible) |
71 | { |
72 | m_capsVisible = capsVisible; |
73 | } |
74 | |
75 | void Candlestick::setIncreasingColor(const QColor &color) |
76 | { |
77 | m_increasingColor = color; |
78 | |
79 | update(); |
80 | } |
81 | |
82 | void Candlestick::setDecreasingColor(const QColor &color) |
83 | { |
84 | m_decreasingColor = color; |
85 | |
86 | update(); |
87 | } |
88 | |
89 | void Candlestick::setBrush(const QBrush &brush) |
90 | { |
91 | m_brush = brush; |
92 | |
93 | update(); |
94 | } |
95 | |
96 | void 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 | |
106 | void Candlestick::setLayout(const CandlestickData &data) |
107 | { |
108 | m_data = data; |
109 | |
110 | updateGeometry(domain: m_domain); |
111 | update(); |
112 | } |
113 | |
114 | void Candlestick::mousePressEvent(QGraphicsSceneMouseEvent *event) |
115 | { |
116 | m_mousePressed = true; |
117 | emit pressed(set: m_set); |
118 | QGraphicsItem::mousePressEvent(event); |
119 | } |
120 | |
121 | void 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 | |
129 | void 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 | |
137 | void 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 | |
146 | void 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 | |
154 | QRectF Candlestick::boundingRect() const |
155 | { |
156 | return m_boundingRect; |
157 | } |
158 | |
159 | void 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 | |
183 | void 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 = (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 = (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 = (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 = m_pen.widthF(); |
320 | m_boundingRect.adjust(xp1: -extra, yp1: -extra, xp2: extra, yp2: extra); |
321 | } |
322 | |
323 | QT_END_NAMESPACE |
324 | |
325 | #include "moc_candlestick_p.cpp" |
326 | |