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