From facc2941efbfb8c9f40e363f0ea881653f6b4393 2016-05-25 11:47:43 From: Alexander Mishin Date: 2016-05-25 11:47:43 Subject: [PATCH] Added candlestick chart type - added QCandlestickSeries - added QCandlestickSet - added QCandlestickLegendMarker - added model mappers - added Candlestick, CandlestickChartItem, CandlestickData - added SeriesTypeCandlestick to SeriesType enum - added LegendMarkerTypeCandlestick to LegendMarkerType enum - added candlestick chart example - added QML candlestick chart example - added candlestick tester - added autotests - added documentation [ChangeLog][CandlestickChart] Added new chart type: Candlestick Chart. Task-number: QTBUG-50544 Change-Id: I17d18dfa23e0ea209bf51ab1e349585b9cb50a8f Reviewed-by: Miikka Heikkinen --- diff --git a/examples/charts/candlestickchart/acme_data.txt b/examples/charts/candlestickchart/acme_data.txt new file mode 100644 index 0000000..eaac318 --- /dev/null +++ b/examples/charts/candlestickchart/acme_data.txt @@ -0,0 +1,24 @@ +# Acme Ltd Historical Data (July 2015) +# timestamp, open, high, low, close +1435708800000 126.90 126.94 125.99 126.60 +1435795200000 126.69 126.69 126.69 126.69 +1436140800000 124.85 126.23 124.85 126.00 +1436227200000 125.89 126.15 123.77 125.69 +1436313600000 124.64 124.64 122.54 122.54 +1436400000000 123.85 124.06 119.22 120.07 +1436486400000 121.94 123.85 121.21 123.28 +1436745600000 125.03 125.76 124.32 125.66 +1436832000000 126.04 126.37 125.04 125.61 +1436918400000 125.72 127.15 125.58 126.82 +1437004800000 127.74 128.57 127.35 128.51 +1437091200000 129.08 129.62 128.31 129.62 +1437350400000 130.97 132.97 130.70 132.07 +1437436800000 132.85 132.92 130.32 130.75 +1437523200000 121.99 125.50 121.99 125.22 +1437609600000 126.20 127.09 125.06 125.16 +1437696000000 125.32 125.74 123.90 124.50 +1437955200000 123.09 123.61 122.12 122.77 +1438041600000 123.38 123.91 122.55 123.38 +1438128000000 123.15 123.50 122.27 122.99 +1438214400000 122.32 122.57 121.71 122.37 +1438300800000 122.60 122.64 120.91 121.30 diff --git a/examples/charts/candlestickchart/candlestickchart.pro b/examples/charts/candlestickchart/candlestickchart.pro new file mode 100644 index 0000000..9a334bd --- /dev/null +++ b/examples/charts/candlestickchart/candlestickchart.pro @@ -0,0 +1,14 @@ +!include( ../examples.pri ) { + error( "Couldn't find the examples.pri file!" ) +} + +TARGET = candlestickchart + +SOURCES += main.cpp \ + candlestickdatareader.cpp + +HEADERS += \ + candlestickdatareader.h + +RESOURCES += \ + candlestickdata.qrc diff --git a/examples/charts/candlestickchart/candlestickdata.qrc b/examples/charts/candlestickchart/candlestickdata.qrc new file mode 100644 index 0000000..e311cad --- /dev/null +++ b/examples/charts/candlestickchart/candlestickdata.qrc @@ -0,0 +1,5 @@ + + + acme_data.txt + + diff --git a/examples/charts/candlestickchart/candlestickdatareader.cpp b/examples/charts/candlestickchart/candlestickdatareader.cpp new file mode 100644 index 0000000..83228ef --- /dev/null +++ b/examples/charts/candlestickchart/candlestickdatareader.cpp @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "candlestickdatareader.h" + +CandlestickDataReader::CandlestickDataReader(QIODevice *device) + : QTextStream(device) +{ +} + +CandlestickDataReader::~CandlestickDataReader() +{ +} + +void CandlestickDataReader::readFile(QIODevice *device) +{ + QTextStream::setDevice(device); +} + +QCandlestickSet *CandlestickDataReader::readCandlestickSet() +{ + //! [1] + QString line = readLine(); + if (line.startsWith("#") || line.isEmpty()) + return 0; + //! [1] + + //! [2] + QStringList strList = line.split(" ", QString::SkipEmptyParts); + if (strList.count() != 5) + return 0; + //! [2] + + //! [3] + const qreal timestamp = strList.at(0).toDouble(); + const qreal open = strList.at(1).toDouble(); + const qreal high = strList.at(2).toDouble(); + const qreal low = strList.at(3).toDouble(); + const qreal close = strList.at(4).toDouble(); + //! [3] + + //! [4] + QCandlestickSet *candlestickSet = new QCandlestickSet(timestamp); + candlestickSet->setOpen(open); + candlestickSet->setHigh(high); + candlestickSet->setLow(low); + candlestickSet->setClose(close); + //! [4] + + return candlestickSet; +} diff --git a/examples/charts/candlestickchart/candlestickdatareader.h b/examples/charts/candlestickchart/candlestickdatareader.h new file mode 100644 index 0000000..aaa83bf --- /dev/null +++ b/examples/charts/candlestickchart/candlestickdatareader.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CANDLESTICKDATAREADER_H +#define CANDLESTICKDATAREADER_H + +#include +#include + +QT_CHARTS_USE_NAMESPACE + +class CandlestickDataReader : public QTextStream +{ +public: + explicit CandlestickDataReader(QIODevice *device); + ~CandlestickDataReader(); + + void readFile(QIODevice *device); + QCandlestickSet *readCandlestickSet(); +}; + +#endif // CANDLESTICKDATAREADER_H diff --git a/examples/charts/candlestickchart/main.cpp b/examples/charts/candlestickchart/main.cpp new file mode 100644 index 0000000..243b0f6 --- /dev/null +++ b/examples/charts/candlestickchart/main.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "candlestickdatareader.h" + +QT_CHARTS_USE_NAMESPACE + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + //! [1] + QCandlestickSeries *acmeSeries = new QCandlestickSeries(); + acmeSeries->setName("Acme Ltd"); + acmeSeries->setIncreasingColor(QColor(Qt::green)); + acmeSeries->setDecreasingColor(QColor(Qt::red)); + //! [1] + + //! [2] + QFile acmeData(":acme"); + if (!acmeData.open(QIODevice::ReadOnly | QIODevice::Text)) + return 1; + + QStringList categories; + + CandlestickDataReader dataReader(&acmeData); + while (!dataReader.atEnd()) { + QCandlestickSet *set = dataReader.readCandlestickSet(); + if (set) { + acmeSeries->append(set); + categories << QDateTime::fromMSecsSinceEpoch(set->timestamp()).toString("dd"); + } + } + //! [2] + + //! [3] + QChart *chart = new QChart(); + chart->addSeries(acmeSeries); + chart->setTitle("Acme Ltd Historical Data (July 2015)"); + chart->setAnimationOptions(QChart::SeriesAnimations); + //! [3] + + //! [4] + chart->createDefaultAxes(); + + QBarCategoryAxis *axisX = qobject_cast(chart->axes(Qt::Horizontal).at(0)); + axisX->setCategories(categories); + + QValueAxis *axisY = qobject_cast(chart->axes(Qt::Vertical).at(0)); + axisY->setMax(axisY->max() * 1.01); + axisY->setMin(axisY->min() * 0.99); + //! [4] + + //! [5] + chart->legend()->setVisible(true); + chart->legend()->setAlignment(Qt::AlignBottom); + //! [5] + + //! [6] + QChartView *chartView = new QChartView(chart); + chartView->setRenderHint(QPainter::Antialiasing); + //! [6] + + //! [7] + QMainWindow window; + window.setCentralWidget(chartView); + window.resize(800, 600); + window.show(); + //! [7] + + return a.exec(); +} diff --git a/examples/charts/charts.pro b/examples/charts/charts.pro index 5caa03c..704d9cc 100644 --- a/examples/charts/charts.pro +++ b/examples/charts/charts.pro @@ -14,6 +14,7 @@ SUBDIRS += areachart \ modeldata \ barchart \ boxplotchart \ + candlestickchart \ legend \ barmodelmapper \ lineandbar \ @@ -36,6 +37,7 @@ SUBDIRS += areachart \ qtHaveModule(quick) { SUBDIRS += qmlboxplot \ + qmlcandlestick \ qmlpiechart \ qmlweather \ qmlf1legends \ diff --git a/examples/charts/qmlcandlestick/main.cpp b/examples/charts/qmlcandlestick/main.cpp new file mode 100644 index 0000000..b399e15 --- /dev/null +++ b/examples/charts/qmlcandlestick/main.cpp @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + // Qt Charts uses Qt Graphics View Framework for drawing, therefore QApplication must be used. + QApplication app(argc, argv); + + QQuickView viewer; + + // The following are needed to make examples run without having to install the module + // in desktop environments. +#ifdef Q_OS_WIN + QString extraImportPath(QStringLiteral("%1/../../../../%2")); +#else + QString extraImportPath(QStringLiteral("%1/../../../%2")); +#endif + viewer.engine()->addImportPath(extraImportPath.arg(QApplication::applicationDirPath(), + QString::fromLatin1("qml"))); + QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close); + + viewer.setTitle(QStringLiteral("QML Candlestick")); + viewer.setSource(QUrl("qrc:/qml/qmlcandlestick/main.qml")); + viewer.setResizeMode(QQuickView::SizeRootObjectToView); + viewer.show(); + + return app.exec(); +} diff --git a/examples/charts/qmlcandlestick/qml/qmlcandlestick/main.qml b/examples/charts/qmlcandlestick/qml/qmlcandlestick/main.qml new file mode 100644 index 0000000..18fca4f --- /dev/null +++ b/examples/charts/qmlcandlestick/qml/qmlcandlestick/main.qml @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtCharts 2.2 + +ChartView { + title: "Candlestick series" + width: 800 + height: 600 + theme: ChartView.ChartThemeLight + legend.alignment: Qt.AlignBottom + antialiasing: true + + CandlestickSeries { + name: "Acme Ltd." + increasingColor: "green" + decreasingColor: "red" + + CandlestickSet { timestamp: 1435708800000; open: 6.90; high: 6.94; low: 5.99; close: 6.60 } + CandlestickSet { timestamp: 1435795200000; open: 6.69; high: 6.69; low: 6.69; close: 6.69 } + CandlestickSet { timestamp: 1436140800000; open: 4.85; high: 6.23; low: 4.85; close: 6.00 } + CandlestickSet { timestamp: 1436227200000; open: 5.89; high: 6.15; low: 3.77; close: 5.69 } + CandlestickSet { timestamp: 1436313600000; open: 4.64; high: 4.64; low: 2.54; close: 2.54 } + } +} diff --git a/examples/charts/qmlcandlestick/qmlcandlestick.pro b/examples/charts/qmlcandlestick/qmlcandlestick.pro new file mode 100644 index 0000000..58e6303 --- /dev/null +++ b/examples/charts/qmlcandlestick/qmlcandlestick.pro @@ -0,0 +1,10 @@ +!include( ../examples.pri ) { + error( "Couldn't find the examples.pri file!" ) +} + +RESOURCES += resources.qrc + +SOURCES += main.cpp + +OTHER_FILES += \ + qml/qmlcandlestick/main.qml diff --git a/examples/charts/qmlcandlestick/resources.qrc b/examples/charts/qmlcandlestick/resources.qrc new file mode 100644 index 0000000..ccfab44 --- /dev/null +++ b/examples/charts/qmlcandlestick/resources.qrc @@ -0,0 +1,5 @@ + + + qml/qmlcandlestick/main.qml + + diff --git a/src/charts/animations/animations.pri b/src/charts/animations/animations.pri index 28b6db1..9a5c86e 100644 --- a/src/charts/animations/animations.pri +++ b/src/charts/animations/animations.pri @@ -11,6 +11,8 @@ SOURCES += \ $$PWD/scatteranimation.cpp \ $$PWD/boxplotanimation.cpp \ $$PWD/boxwhiskersanimation.cpp \ + $$PWD/candlestickanimation.cpp \ + $$PWD/candlestickbodywicksanimation.cpp \ $$PWD/chartanimation.cpp PRIVATE_HEADERS += \ @@ -23,4 +25,6 @@ PRIVATE_HEADERS += \ $$PWD/baranimation_p.h \ $$PWD/scatteranimation_p.h \ $$PWD/boxplotanimation_p.h \ - $$PWD/boxwhiskersanimation_p.h + $$PWD/boxwhiskersanimation_p.h \ + $$PWD/candlestickanimation_p.h \ + $$PWD/candlestickbodywicksanimation_p.h diff --git a/src/charts/animations/candlestickanimation.cpp b/src/charts/animations/candlestickanimation.cpp new file mode 100644 index 0000000..5e14194 --- /dev/null +++ b/src/charts/animations/candlestickanimation.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +CandlestickAnimation::CandlestickAnimation(CandlestickChartItem *item, int duration, + QEasingCurve &curve) + : QObject(item), + m_item(item), + m_animationDuration(duration), + m_animationCurve(curve) +{ +} + +CandlestickAnimation::~CandlestickAnimation() +{ +} + +void CandlestickAnimation::addCandlestick(Candlestick *candlestick) +{ + CandlestickBodyWicksAnimation *animation = m_animations.value(candlestick, 0); + if (!animation) { + animation = new CandlestickBodyWicksAnimation(candlestick, this, m_animationDuration, + m_animationCurve); + m_animations.insert(candlestick, animation); + + qreal median = (candlestick->m_data.m_open + candlestick->m_data.m_close) / 2; + CandlestickData start; + start.m_open = median; + start.m_high = median; + start.m_low = median; + start.m_close = median; + animation->setup(start, candlestick->m_data); + } else { + animation->stop(); + animation->setEndData(candlestick->m_data); + } +} + +ChartAnimation *CandlestickAnimation::candlestickAnimation(Candlestick *candlestick) +{ + CandlestickBodyWicksAnimation *animation = m_animations.value(candlestick, 0); + if (animation) + animation->m_changeAnimation = false; + + return animation; +} + +ChartAnimation *CandlestickAnimation::candlestickChangeAnimation(Candlestick *candlestick) +{ + CandlestickBodyWicksAnimation *animation = m_animations.value(candlestick, 0); + if (animation) { + animation->m_changeAnimation = true; + animation->setEndData(candlestick->m_data); + } + + return animation; +} + +void CandlestickAnimation::setAnimationStart(Candlestick *candlestick) +{ + CandlestickBodyWicksAnimation *animation = m_animations.value(candlestick, 0); + if (animation) + animation->setStartData(candlestick->m_data); +} + +void CandlestickAnimation::stopAll() +{ + foreach (Candlestick *candlestick, m_animations.keys()) { + CandlestickBodyWicksAnimation *animation = m_animations.value(candlestick, 0); + if (animation) + animation->stopAndDestroyLater(); + m_animations.remove(candlestick); + } +} + +void CandlestickAnimation::removeCandlestickAnimation(Candlestick *candlestick) +{ + m_animations.remove(candlestick); +} + +#include "moc_candlestickanimation_p.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/animations/candlestickanimation_p.h b/src/charts/animations/candlestickanimation_p.h new file mode 100644 index 0000000..cb894cd --- /dev/null +++ b/src/charts/animations/candlestickanimation_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef CANDLESTICKANIMATION_P_H +#define CANDLESTICKANIMATION_P_H + +#include + +QT_CHARTS_BEGIN_NAMESPACE + +class Candlestick; +class CandlestickChartItem; +class CandlestickBodyWicksAnimation; + +class CandlestickAnimation : public QObject +{ + Q_OBJECT + +public: + CandlestickAnimation(CandlestickChartItem *item, int duration, QEasingCurve &curve); + ~CandlestickAnimation(); + + void addCandlestick(Candlestick *candlestick); + ChartAnimation *candlestickAnimation(Candlestick *candlestick); + ChartAnimation *candlestickChangeAnimation(Candlestick *candlestick); + + void setAnimationStart(Candlestick *candlestick); + void stopAll(); + void removeCandlestickAnimation(Candlestick *candlestick); + +protected: + QHash m_animations; + CandlestickChartItem *m_item; + int m_animationDuration; + QEasingCurve m_animationCurve; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // CANDLESTICKANIMATION_P_H diff --git a/src/charts/animations/candlestickbodywicksanimation.cpp b/src/charts/animations/candlestickbodywicksanimation.cpp new file mode 100644 index 0000000..615f43c --- /dev/null +++ b/src/charts/animations/candlestickbodywicksanimation.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(QT_CHARTS_NAMESPACE::CandlestickData) +Q_DECLARE_METATYPE(qreal) + +QT_CHARTS_BEGIN_NAMESPACE + +CandlestickBodyWicksAnimation::CandlestickBodyWicksAnimation(Candlestick *candlestick, + CandlestickAnimation *animation, + int duration, QEasingCurve &curve) + : ChartAnimation(candlestick), + m_candlestick(candlestick), + m_candlestickAnimation(animation), + m_changeAnimation(false) +{ + setDuration(duration); + setEasingCurve(curve); +} + +CandlestickBodyWicksAnimation::~CandlestickBodyWicksAnimation() +{ + if (m_candlestickAnimation) + m_candlestickAnimation->removeCandlestickAnimation(m_candlestick); +} + +void CandlestickBodyWicksAnimation::setup(const CandlestickData &startData, + const CandlestickData &endData) +{ + setKeyValueAt(0.0, qVariantFromValue(startData)); + setKeyValueAt(1.0, qVariantFromValue(endData)); +} + +void CandlestickBodyWicksAnimation::setStartData(const CandlestickData &startData) +{ + if (state() != QAbstractAnimation::Stopped) + stop(); + + setStartValue(qVariantFromValue(startData)); +} + +void CandlestickBodyWicksAnimation::setEndData(const CandlestickData &endData) +{ + if (state() != QAbstractAnimation::Stopped) + stop(); + + setEndValue(qVariantFromValue(endData)); +} + +void CandlestickBodyWicksAnimation::updateCurrentValue(const QVariant &value) +{ + CandlestickData data = qvariant_cast(value); + m_candlestick->setLayout(data); +} + +QVariant CandlestickBodyWicksAnimation::interpolated(const QVariant &from, const QVariant &to, + qreal progress) const +{ + CandlestickData startData = qvariant_cast(from); + CandlestickData endData = qvariant_cast(to); + CandlestickData result = endData; + + if (m_changeAnimation) { + result.m_open = startData.m_open + progress * (endData.m_open - startData.m_open); + result.m_high = startData.m_high + progress * (endData.m_high - startData.m_high); + result.m_low = startData.m_low + progress * (endData.m_low - startData.m_low); + result.m_close = startData.m_close + progress * (endData.m_close - startData.m_close); + } else { + const qreal median = (endData.m_open + endData.m_close) / 2; + result.m_low = median + progress * (endData.m_low - median); + result.m_close = median + progress * (endData.m_close - median); + result.m_open = median + progress * (endData.m_open - median); + result.m_high = median + progress * (endData.m_high - median); + } + + return qVariantFromValue(result); +} + +#include "moc_candlestickbodywicksanimation_p.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/animations/candlestickbodywicksanimation_p.h b/src/charts/animations/candlestickbodywicksanimation_p.h new file mode 100644 index 0000000..a8d30f0 --- /dev/null +++ b/src/charts/animations/candlestickbodywicksanimation_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef CANDLESTICKBODYWICKSANIMATION_P_H +#define CANDLESTICKBODYWICKSANIMATION_P_H + +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +class Candlestick; +class CandlestickAnimation; + +class CandlestickBodyWicksAnimation : public ChartAnimation +{ + Q_OBJECT + +public: + CandlestickBodyWicksAnimation(Candlestick *candlestick, CandlestickAnimation *animation, + int duration, QEasingCurve &curve); + ~CandlestickBodyWicksAnimation(); + + void setup(const CandlestickData &startData, const CandlestickData &endData); + void setStartData(const CandlestickData &startData); + void setEndData(const CandlestickData &endData); + + // from QVariantAnimation + virtual void updateCurrentValue(const QVariant &value); + virtual QVariant interpolated(const QVariant &from, const QVariant &to, qreal progress) const; + +protected: + Candlestick *m_candlestick; + CandlestickAnimation *m_candlestickAnimation; + bool m_changeAnimation; + + friend class CandlestickAnimation; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // CANDLESTICKBODYWICKSANIMATION_P_H diff --git a/src/charts/barchart/qabstractbarseries.cpp b/src/charts/barchart/qabstractbarseries.cpp index 027ae8d..844b21e 100644 --- a/src/charts/barchart/qabstractbarseries.cpp +++ b/src/charts/barchart/qabstractbarseries.cpp @@ -983,6 +983,7 @@ void QAbstractBarSeriesPrivate::initializeAxes() case QAbstractSeries::SeriesTypePercentBar: case QAbstractSeries::SeriesTypeStackedBar: case QAbstractSeries::SeriesTypeBoxPlot: + case QAbstractSeries::SeriesTypeCandlestick: if (axis->orientation() == Qt::Horizontal) populateCategories(qobject_cast(axis)); break; @@ -1009,6 +1010,7 @@ QAbstractAxis::AxisType QAbstractBarSeriesPrivate::defaultAxisType(Qt::Orientati case QAbstractSeries::SeriesTypePercentBar: case QAbstractSeries::SeriesTypeStackedBar: case QAbstractSeries::SeriesTypeBoxPlot: + case QAbstractSeries::SeriesTypeCandlestick: if (orientation == Qt::Horizontal) return QAbstractAxis::AxisTypeBarCategory; break; diff --git a/src/charts/candlestickchart/candlestick.cpp b/src/charts/candlestickchart/candlestick.cpp new file mode 100644 index 0000000..9e8d8fa --- /dev/null +++ b/src/charts/candlestickchart/candlestick.cpp @@ -0,0 +1,351 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +Candlestick::Candlestick(QCandlestickSet *set, AbstractDomain *domain, QGraphicsObject *parent) + : QGraphicsObject(parent), + m_set(set), + m_domain(domain), + m_timePeriod(0.0), + m_maximumColumnWidth(-1.0), // no maximum column width by default + m_minimumColumnWidth(-1.0), // no minimum column width by default + m_bodyWidth(0.5), + m_bodyOutlineVisible(true), + m_capsWidth(0.5), + m_capsVisible(false), + m_brush(QChartPrivate::defaultBrush()), + m_pen(QChartPrivate::defaultPen()), + m_hovering(false), + m_mousePressed(false) +{ + setAcceptHoverEvents(true); + setAcceptedMouseButtons(Qt::MouseButtonMask); + setFlag(QGraphicsObject::ItemIsSelectable); +} + +Candlestick::~Candlestick() +{ + // End hover event, if candlestick is deleted during it. + if (m_hovering) + emit hovered(false, m_set); +} + +void Candlestick::setTimePeriod(qreal timePeriod) +{ + m_timePeriod = timePeriod; +} + +void Candlestick::setMaximumColumnWidth(qreal maximumColumnWidth) +{ + m_maximumColumnWidth = maximumColumnWidth; +} + +void Candlestick::setMinimumColumnWidth(qreal minimumColumnWidth) +{ + m_minimumColumnWidth = minimumColumnWidth; +} + +void Candlestick::setBodyWidth(qreal bodyWidth) +{ + m_bodyWidth = bodyWidth; +} + +void Candlestick::setBodyOutlineVisible(bool bodyOutlineVisible) +{ + m_bodyOutlineVisible = bodyOutlineVisible; +} + +void Candlestick::setCapsWidth(qreal capsWidth) +{ + m_capsWidth = capsWidth; +} + +void Candlestick::setCapsVisible(bool capsVisible) +{ + m_capsVisible = capsVisible; +} + +void Candlestick::setIncreasingColor(const QColor &color) +{ + m_increasingColor = color; + + update(); +} + +void Candlestick::setDecreasingColor(const QColor &color) +{ + m_decreasingColor = color; + + update(); +} + +void Candlestick::setBrush(const QBrush &brush) +{ + m_brush = brush; + + update(); +} + +void Candlestick::setPen(const QPen &pen) +{ + qreal widthDiff = pen.widthF() - m_pen.widthF(); + m_boundingRect.adjust(-widthDiff, -widthDiff, widthDiff, widthDiff); + + m_pen = pen; + + update(); +} + +void Candlestick::setLayout(const CandlestickData &data) +{ + m_data = data; + + updateGeometry(m_domain); + update(); +} + +void Candlestick::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + m_mousePressed = true; + emit pressed(m_set); + QGraphicsItem::mousePressEvent(event); +} + +void Candlestick::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event) + + m_hovering = true; + emit hovered(m_hovering, m_set); +} + +void Candlestick::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event) + + m_hovering = false; + emit hovered(m_hovering, m_set); +} + +void Candlestick::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + emit released(m_set); + if (m_mousePressed) + emit clicked(m_set); + m_mousePressed = false; + QGraphicsItem::mouseReleaseEvent(event); +} + +void Candlestick::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + // For candlestick a pressed signal needs to be explicitly fired for mouseDoubleClickEvent. + emit pressed(m_set); + emit doubleClicked(m_set); + QGraphicsItem::mouseDoubleClickEvent(event); +} + +QRectF Candlestick::boundingRect() const +{ + return m_boundingRect; +} + +void Candlestick::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + bool increasingTrend = (m_data.m_open < m_data.m_close); + QColor color = increasingTrend ? m_increasingColor : m_decreasingColor; + + QBrush brush(m_brush); + brush.setColor(color); + + painter->save(); + painter->setBrush(brush); + painter->setPen(m_pen); + painter->setClipRect(m_boundingRect); + if (m_capsVisible) + painter->drawPath(m_capsPath); + painter->drawPath(m_wicksPath); + if (!m_bodyOutlineVisible) + painter->setPen(QColor(Qt::transparent)); + painter->drawRect(m_bodyRect); + painter->restore(); +} + +void Candlestick::updateGeometry(AbstractDomain *domain) +{ + m_domain = domain; + + prepareGeometryChange(); + + m_capsPath = QPainterPath(); + m_wicksPath = QPainterPath(); + m_boundingRect = QRectF(); + + if (!m_data.m_series->chart()) + return; + + QList axes = m_data.m_series->chart()->axes(Qt::Horizontal, m_data.m_series); + if (axes.isEmpty()) + return; + + QAbstractAxis *axisX = axes.value(0); + if (!axisX) + return; + + qreal columnWidth = 0.0; + qreal columnCenter = 0.0; + switch (axisX->type()) { + case QAbstractAxis::AxisTypeBarCategory: + columnWidth = 1.0 / m_data.m_seriesCount; + columnCenter = m_data.m_index - 0.5 + + m_data.m_seriesIndex * columnWidth + + columnWidth / 2.0; + break; + case QAbstractAxis::AxisTypeDateTime: + case QAbstractAxis::AxisTypeValue: + columnWidth = m_timePeriod; + columnCenter = m_data.m_timestamp; + break; + default: + qWarning() << "Unexpected axis type"; + return; + } + + const qreal bodyWidth = m_bodyWidth * columnWidth; + const qreal bodyLeft = columnCenter - (bodyWidth / 2.0); + const qreal bodyRight = bodyLeft + bodyWidth; + + const qreal upperBody = qMax(m_data.m_open, m_data.m_close); + const qreal lowerBody = qMin(m_data.m_open, m_data.m_close); + const bool upperWickVisible = (m_data.m_high > upperBody); + const bool lowerWickVisible = (m_data.m_low < lowerBody); + + QPointF geometryPoint; + bool validData; + + // upper extreme + geometryPoint = m_domain->calculateGeometryPoint(QPointF(bodyLeft, m_data.m_high), validData); + if (!validData) + return; + const qreal geometryUpperExtreme = geometryPoint.y(); + // upper body + geometryPoint = m_domain->calculateGeometryPoint(QPointF(bodyLeft, upperBody), validData); + if (!validData) + return; + const qreal geometryBodyLeft = geometryPoint.x(); + const qreal geometryUpperBody = geometryPoint.y(); + // lower body + geometryPoint = m_domain->calculateGeometryPoint(QPointF(bodyRight, lowerBody), validData); + if (!validData) + return; + const qreal geometryBodyRight = geometryPoint.x(); + const qreal geometryLowerBody = geometryPoint.y(); + // lower extreme + geometryPoint = m_domain->calculateGeometryPoint(QPointF(bodyRight, m_data.m_low), validData); + if (!validData) + return; + const qreal geometryLowerExtreme = geometryPoint.y(); + + // Real Body + m_bodyRect.setCoords(geometryBodyLeft, geometryUpperBody, geometryBodyRight, geometryLowerBody); + if (m_maximumColumnWidth != -1.0) { + if (m_bodyRect.width() > m_maximumColumnWidth) { + qreal extra = (m_bodyRect.width() - m_maximumColumnWidth) / 2.0; + m_bodyRect.adjust(extra, 0.0, 0.0, 0.0); + m_bodyRect.setWidth(m_maximumColumnWidth); + } + } + if (m_minimumColumnWidth != -1.0) { + if (m_bodyRect.width() < m_minimumColumnWidth) { + qreal extra = (m_minimumColumnWidth - m_bodyRect.width()) / 2.0; + m_bodyRect.adjust(-extra, 0.0, 0.0, 0.0); + m_bodyRect.setWidth(m_minimumColumnWidth); + } + } + + const qreal geometryCapsExtra = (m_bodyRect.width() - (m_bodyRect.width() * m_capsWidth)) /2.0; + const qreal geometryCapsLeft = m_bodyRect.left() + geometryCapsExtra; + const qreal geometryCapsRight = m_bodyRect.right() - geometryCapsExtra; + + // Upper Wick and Cap + if (upperWickVisible) { + m_capsPath.moveTo(geometryCapsLeft, geometryUpperExtreme); + m_capsPath.lineTo(geometryCapsRight, geometryUpperExtreme); + m_wicksPath.moveTo((geometryCapsLeft + geometryCapsRight) / 2.0, geometryUpperExtreme); + m_wicksPath.lineTo((geometryCapsLeft + geometryCapsRight) / 2.0, geometryUpperBody); + } + // Lower Wick and Cap + if (lowerWickVisible) { + m_capsPath.moveTo(geometryCapsLeft, geometryLowerExtreme); + m_capsPath.lineTo(geometryCapsRight, geometryLowerExtreme); + m_wicksPath.moveTo((geometryCapsLeft + geometryCapsRight) / 2.0, geometryLowerBody); + m_wicksPath.lineTo((geometryCapsLeft + geometryCapsRight) / 2.0, geometryLowerExtreme); + } + m_wicksPath.closeSubpath(); + + // bounding rectangle top + qreal boundingRectTop; + if (upperWickVisible) + boundingRectTop = m_wicksPath.boundingRect().top(); + else + boundingRectTop = m_bodyRect.top(); + boundingRectTop = qMax(boundingRectTop, parentItem()->boundingRect().top()); + // bounding rectangle right + qreal boundingRectRight = qMin(m_bodyRect.right(), parentItem()->boundingRect().right()); + // bounding rectangle bottom + qreal boundingRectBottom; + if (lowerWickVisible) + boundingRectBottom = m_wicksPath.boundingRect().bottom(); + else + boundingRectBottom = m_bodyRect.bottom(); + boundingRectBottom = qMin(boundingRectBottom, parentItem()->boundingRect().bottom()); + // bounding rectangle left + qreal boundingRectLeft = qMax(m_bodyRect.left(), parentItem()->boundingRect().left()); + + m_boundingRect.setTop(boundingRectTop); + m_boundingRect.setRight(boundingRectRight); + m_boundingRect.setBottom(boundingRectBottom); + m_boundingRect.setLeft(boundingRectLeft); + + qreal extra = m_pen.widthF(); + m_boundingRect.adjust(-extra, -extra, extra, extra); +} + +#include "moc_candlestick_p.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/candlestickchart/candlestick_p.h b/src/charts/candlestickchart/candlestick_p.h new file mode 100644 index 0000000..fb39bbf --- /dev/null +++ b/src/charts/candlestickchart/candlestick_p.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef CANDLESTICK_P_H +#define CANDLESTICK_P_H + +#include +#include +#include +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +class AbstractDomain; +class QCandlestickSet; + +class Candlestick : public QGraphicsObject +{ + Q_OBJECT + +public: + Candlestick(QCandlestickSet *set, AbstractDomain *domain, QGraphicsObject *parent); + ~Candlestick(); + + void setTimePeriod(qreal timePeriod); + void setMaximumColumnWidth(qreal maximumColumnWidth); + void setMinimumColumnWidth(qreal minimumColumnWidth); + void setBodyWidth(qreal bodyWidth); + void setBodyOutlineVisible(bool bodyOutlineVisible); + void setCapsWidth(qreal capsWidth); + void setCapsVisible(bool capsVisible); + void setIncreasingColor(const QColor &color); + void setDecreasingColor(const QColor &color); + void setBrush(const QBrush &brush); + void setPen(const QPen &pen); + void setLayout(const CandlestickData &data); + + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget = nullptr); + +Q_SIGNALS: + void clicked(QCandlestickSet *set); + void hovered(bool status, QCandlestickSet *set); + void pressed(QCandlestickSet *set); + void released(QCandlestickSet *set); + void doubleClicked(QCandlestickSet *set); + +private: + void updateGeometry(AbstractDomain *domain); + +private: + QCandlestickSet *m_set; + AbstractDomain *m_domain; + qreal m_timePeriod; + qreal m_maximumColumnWidth; + qreal m_minimumColumnWidth; + qreal m_bodyWidth; + bool m_bodyOutlineVisible; + qreal m_capsWidth; + bool m_capsVisible; + QColor m_increasingColor; + QColor m_decreasingColor; + QBrush m_brush; + QPen m_pen; + CandlestickData m_data; + bool m_hovering; + bool m_mousePressed; + QRectF m_boundingRect; + QRectF m_bodyRect; + QPainterPath m_wicksPath; + QPainterPath m_capsPath; + + friend class CandlestickAnimation; + friend class CandlestickChartItem; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // CANDLESTICK_P_H diff --git a/src/charts/candlestickchart/candlestickchart.pri b/src/charts/candlestickchart/candlestickchart.pri new file mode 100644 index 0000000..35cdd37 --- /dev/null +++ b/src/charts/candlestickchart/candlestickchart.pri @@ -0,0 +1,26 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD + +SOURCES += \ + $$PWD/candlestick.cpp \ + $$PWD/candlestickchartitem.cpp \ + $$PWD/qcandlestickseries.cpp \ + $$PWD/qcandlestickset.cpp \ + $$PWD/qcandlestickmodelmapper.cpp \ + $$PWD/qhcandlestickmodelmapper.cpp \ + $$PWD/qvcandlestickmodelmapper.cpp + +PRIVATE_HEADERS += \ + $$PWD/candlestick_p.h \ + $$PWD/candlestickchartitem_p.h \ + $$PWD/candlestickdata_p.h \ + $$PWD/qcandlestickseries_p.h \ + $$PWD/qcandlestickset_p.h \ + $$PWD/qcandlestickmodelmapper_p.h + +PUBLIC_HEADERS += \ + $$PWD/qcandlestickseries.h \ + $$PWD/qcandlestickset.h \ + $$PWD/qcandlestickmodelmapper.h \ + $$PWD/qhcandlestickmodelmapper.h \ + $$PWD/qvcandlestickmodelmapper.h diff --git a/src/charts/candlestickchart/candlestickchartitem.cpp b/src/charts/candlestickchart/candlestickchartitem.cpp new file mode 100644 index 0000000..ad64dd1 --- /dev/null +++ b/src/charts/candlestickchart/candlestickchartitem.cpp @@ -0,0 +1,347 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +CandlestickChartItem::CandlestickChartItem(QCandlestickSeries *series, QGraphicsItem *item) + : ChartItem(series->d_func(), item), + m_series(series), + m_seriesIndex(0), + m_seriesCount(0), + m_timePeriod(0.0), + m_animation(nullptr) +{ + connect(series, SIGNAL(candlestickSetsAdded(QList)), + this, SLOT(handleCandlestickSetsAdd(QList))); + connect(series, SIGNAL(candlestickSetsRemoved(QList)), + this, SLOT(handleCandlestickSetsRemove(QList))); + + connect(series->d_func(), SIGNAL(updated()), this, SLOT(handleCandlesticksUpdated())); + connect(series->d_func(), SIGNAL(updatedLayout()), this, SLOT(handleLayoutUpdated())); + connect(series->d_func(), SIGNAL(updatedCandlesticks()), + this, SLOT(handleCandlesticksUpdated())); + + setZValue(ChartPresenter::CandlestickSeriesZValue); + + handleCandlestickSetsAdd(m_series->candlestickSets()); +} + +CandlestickChartItem::~CandlestickChartItem() +{ +} + +void CandlestickChartItem::setAnimation(CandlestickAnimation *animation) +{ + m_animation = animation; + + if (m_animation) { + foreach (Candlestick *item, m_candlesticks.values()) + m_animation->addCandlestick(item); + + handleDomainUpdated(); + } +} + +QRectF CandlestickChartItem::boundingRect() const +{ + return m_boundingRect; +} + +void CandlestickChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_UNUSED(painter); + Q_UNUSED(option); + Q_UNUSED(widget); +} + +void CandlestickChartItem::handleDomainUpdated() +{ + if ((domain()->size().width() <= 0) || (domain()->size().height() <= 0)) + return; + + // Set bounding rectangle to same as domain size. Add one pixel at the top (-1.0) and the bottom + // as 0.0 would snip a bit off from the wick at the grid line. + m_boundingRect.setRect(0.0, -1.0, domain()->size().width(), domain()->size().height() + 1.0); + + foreach (Candlestick *item, m_candlesticks.values()) { + item->updateGeometry(domain()); + + if (m_animation) + presenter()->startAnimation(m_animation->candlestickAnimation(item)); + } +} + +void CandlestickChartItem::handleLayoutUpdated() +{ + bool timestampChanged = false; + foreach (QCandlestickSet *set, m_candlesticks.keys()) { + qreal oldTimestamp = m_candlesticks.value(set)->m_data.m_timestamp; + qreal newTimestamp = set->timestamp(); + if (Q_UNLIKELY(oldTimestamp != newTimestamp)) { + removeTimestamp(oldTimestamp); + addTimestamp(newTimestamp); + timestampChanged = true; + } + } + if (timestampChanged) + updateTimePeriod(); + + foreach (Candlestick *item, m_candlesticks.values()) { + if (m_animation) + m_animation->setAnimationStart(item); + + item->setTimePeriod(m_timePeriod); + item->setMaximumColumnWidth(m_series->maximumColumnWidth()); + item->setMinimumColumnWidth(m_series->minimumColumnWidth()); + item->setBodyWidth(m_series->bodyWidth()); + item->setCapsWidth(m_series->capsWidth()); + + bool dirty = updateCandlestickGeometry(item, item->m_data.m_index); + if (dirty && m_animation) + presenter()->startAnimation(m_animation->candlestickChangeAnimation(item)); + else + item->updateGeometry(domain()); + } +} + +void CandlestickChartItem::handleCandlesticksUpdated() +{ + foreach (QCandlestickSet *set, m_candlesticks.keys()) + updateCandlestickAppearance(m_candlesticks.value(set), set); +} + +void CandlestickChartItem::handleCandlestickSeriesChange() +{ + int seriesIndex = 0; + int seriesCount = 0; + + int index = 0; + foreach (QAbstractSeries *series, m_series->chart()->series()) { + if (series->type() == QAbstractSeries::SeriesTypeCandlestick) { + if (m_series == static_cast(series)) + seriesIndex = index; + index++; + } + } + seriesCount = index; + + bool changed; + if ((m_seriesIndex != seriesIndex) || (m_seriesCount != seriesCount)) + changed = true; + else + changed = false; + + if (changed) { + m_seriesIndex = seriesIndex; + m_seriesCount = seriesCount; + handleDataStructureChanged(); + } +} + +void CandlestickChartItem::handleCandlestickSetsAdd(const QList &sets) +{ + foreach (QCandlestickSet *set, sets) { + Candlestick *item = m_candlesticks.value(set, 0); + if (item) { + qWarning() << "There is already a candlestick for this set in the hash"; + continue; + } + + item = new Candlestick(set, domain(), this); + m_candlesticks.insert(set, item); + addTimestamp(set->timestamp()); + + connect(item, SIGNAL(clicked(QCandlestickSet *)), + m_series, SIGNAL(clicked(QCandlestickSet *))); + connect(item, SIGNAL(hovered(bool, QCandlestickSet *)), + m_series, SIGNAL(hovered(bool, QCandlestickSet *))); + connect(item, SIGNAL(pressed(QCandlestickSet *)), + m_series, SIGNAL(pressed(QCandlestickSet *))); + connect(item, SIGNAL(released(QCandlestickSet *)), + m_series, SIGNAL(released(QCandlestickSet *))); + connect(item, SIGNAL(doubleClicked(QCandlestickSet *)), + m_series, SIGNAL(doubleClicked(QCandlestickSet *))); + connect(item, SIGNAL(clicked(QCandlestickSet *)), set, SIGNAL(clicked())); + connect(item, SIGNAL(hovered(bool, QCandlestickSet *)), set, SIGNAL(hovered(bool))); + connect(item, SIGNAL(pressed(QCandlestickSet *)), set, SIGNAL(pressed())); + connect(item, SIGNAL(released(QCandlestickSet *)), set, SIGNAL(released())); + connect(item, SIGNAL(doubleClicked(QCandlestickSet *)), set, SIGNAL(doubleClicked())); + } + + handleDataStructureChanged(); +} + +void CandlestickChartItem::handleCandlestickSetsRemove(const QList &sets) +{ + foreach (QCandlestickSet *set, sets) { + Candlestick *item = m_candlesticks.value(set); + + m_candlesticks.remove(set); + removeTimestamp(set->timestamp()); + + if (m_animation) { + ChartAnimation *animation = m_animation->candlestickAnimation(item); + if (animation) { + animation->stop(); + delete animation; + } + } + + delete item; + } + + handleDataStructureChanged(); +} + +void CandlestickChartItem::handleDataStructureChanged() +{ + updateTimePeriod(); + + for (int i = 0; i < m_series->count(); ++i) { + QCandlestickSet *set = m_series->candlestickSets().at(i); + Candlestick *item = m_candlesticks.value(set); + + updateCandlestickGeometry(item, i); + updateCandlestickAppearance(item, set); + + item->updateGeometry(domain()); + + if (m_animation) + m_animation->addCandlestick(item); + } + + handleDomainUpdated(); +} + +bool CandlestickChartItem::updateCandlestickGeometry(Candlestick *item, int index) +{ + bool changed = false; + + QCandlestickSet *set = m_series->candlestickSets().at(index); + CandlestickData &data = item->m_data; + + if ((data.m_open != set->open()) + || (data.m_high != set->high()) + || (data.m_low != set->low()) + || (data.m_close != set->close())) { + changed = true; + } + + data.m_timestamp = set->timestamp(); + data.m_open = set->open(); + data.m_high = set->high(); + data.m_low = set->low(); + data.m_close = set->close(); + data.m_index = index; + + data.m_maxX = domain()->maxX(); + data.m_minX = domain()->minX(); + data.m_maxY = domain()->maxY(); + data.m_minY = domain()->minY(); + + data.m_series = m_series; + data.m_seriesIndex = m_seriesIndex; + data.m_seriesCount = m_seriesCount; + + return changed; +} + +void CandlestickChartItem::updateCandlestickAppearance(Candlestick *item, QCandlestickSet *set) +{ + item->setTimePeriod(m_timePeriod); + item->setMaximumColumnWidth(m_series->maximumColumnWidth()); + item->setMinimumColumnWidth(m_series->minimumColumnWidth()); + item->setBodyWidth(m_series->bodyWidth()); + item->setBodyOutlineVisible(m_series->bodyOutlineVisible()); + item->setCapsWidth(m_series->capsWidth()); + item->setCapsVisible(m_series->capsVisible()); + item->setIncreasingColor(m_series->increasingColor()); + item->setDecreasingColor(m_series->decreasingColor()); + + // Set the decorative issues for the candlestick so that + // the brush and pen already defined for the set are kept. + if (set->brush() == Qt::NoBrush) + item->setBrush(m_series->brush()); + else + item->setBrush(set->brush()); + + if (set->pen() == Qt::NoPen) + item->setPen(m_series->pen()); + else + item->setPen(set->pen()); +} + +void CandlestickChartItem::addTimestamp(qreal timestamp) +{ + int index = 0; + for (int i = m_timestamps.count() - 1; i >= 0; --i) { + if (timestamp > m_timestamps.at(i)) { + index = i + 1; + break; + } + } + m_timestamps.insert(index, timestamp); +} + +void CandlestickChartItem::removeTimestamp(qreal timestamp) +{ + m_timestamps.removeOne(timestamp); +} + +void CandlestickChartItem::updateTimePeriod() +{ + if (m_timestamps.count() == 0) { + m_timePeriod = 0; + return; + } + + if (m_timestamps.count() == 1) { + m_timePeriod = qAbs(domain()->maxX() - domain()->minX()); + return; + } + + qreal timePeriod = qAbs(m_timestamps.at(1) - m_timestamps.at(0)); + for (int i = 1; i < m_timestamps.count(); ++i) { + timePeriod = qMin(timePeriod, qAbs(m_timestamps.at(i) - m_timestamps.at(i - 1))); + } + m_timePeriod = timePeriod; +} + +#include "moc_candlestickchartitem_p.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/candlestickchart/candlestickchartitem_p.h b/src/charts/candlestickchart/candlestickchartitem_p.h new file mode 100644 index 0000000..ee3b1f7 --- /dev/null +++ b/src/charts/candlestickchart/candlestickchartitem_p.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef CANDLESTICKCHARTITEM_P_H +#define CANDLESTICKCHARTITEM_P_H + +#include + +QT_CHARTS_BEGIN_NAMESPACE + +class Candlestick; +class CandlestickAnimation; +class QCandlestickSeries; +class QCandlestickSet; + +class CandlestickChartItem : public ChartItem +{ + Q_OBJECT + +public: + CandlestickChartItem(QCandlestickSeries *series, QGraphicsItem *item = nullptr); + ~CandlestickChartItem(); + + void setAnimation(CandlestickAnimation *animation); + + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + +public Q_SLOTS: + void handleDomainUpdated(); + void handleLayoutUpdated(); + void handleCandlesticksUpdated(); + void handleCandlestickSeriesChange(); + +private Q_SLOTS: + void handleCandlestickSetsAdd(const QList &sets); + void handleCandlestickSetsRemove(const QList &sets); + void handleDataStructureChanged(); + +private: + bool updateCandlestickGeometry(Candlestick *item, int index); + void updateCandlestickAppearance(Candlestick *item, QCandlestickSet *set); + + void addTimestamp(qreal timestamp); + void removeTimestamp(qreal timestamp); + void updateTimePeriod(); + +protected: + QRectF m_boundingRect; + QCandlestickSeries *m_series; // Not owned. + int m_seriesIndex; + int m_seriesCount; + QHash m_candlesticks; + QList m_timestamps; + qreal m_timePeriod; + CandlestickAnimation *m_animation; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // CANDLESTICKCHARTITEM_P_H diff --git a/src/charts/candlestickchart/candlestickdata_p.h b/src/charts/candlestickchart/candlestickdata_p.h new file mode 100644 index 0000000..8cb1ab8 --- /dev/null +++ b/src/charts/candlestickchart/candlestickdata_p.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef CANDLESTICKDATA_P_H +#define CANDLESTICKDATA_P_H + +#include + +QT_CHARTS_BEGIN_NAMESPACE + +class CandlestickData +{ +public: + CandlestickData() : + m_timestamp(0.0), + m_open(0.0), + m_high(0.0), + m_low(0.0), + m_close(0.0), + m_index(0), + m_maxX(0.0), + m_minX(0.0), + m_maxY(0.0), + m_minY(0.0), + m_series(nullptr), + m_seriesIndex(0), + m_seriesCount(0) + { + } + + // Candlestick related statistics + qreal m_timestamp; + qreal m_open; + qreal m_high; + qreal m_low; + qreal m_close; + int m_index; + + // Domain boundaries + qreal m_maxX; + qreal m_minX; + qreal m_maxY; + qreal m_minY; + + // Series related data + QCandlestickSeries *m_series; + int m_seriesIndex; + int m_seriesCount; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // CANDLESTICKDATA_P_H diff --git a/src/charts/candlestickchart/qcandlestickmodelmapper.cpp b/src/charts/candlestickchart/qcandlestickmodelmapper.cpp new file mode 100644 index 0000000..a66e82a --- /dev/null +++ b/src/charts/candlestickchart/qcandlestickmodelmapper.cpp @@ -0,0 +1,706 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +/*! + \class QCandlestickModelMapper + \since 5.8 + \inmodule Qt Charts + \brief Abstract model mapper class for candlestick series. + + Model mappers allow the use of a QAbstractItemModel-derived model as a data source for a chart + series, creating a connection between a QCandlestickSeries and the model object. A model mapper + maintains an equal size across all \l {QCandlestickSet} {QCandlestickSets}. + + \note The model used must support adding and removing rows/columns and modifying the data of the + cells. +*/ + +/*! + \property QCandlestickModelMapper::model + \brief Defines the model that is used by the mapper. +*/ + +/*! + \property QCandlestickModelMapper::series + \brief Defines the QCandlestickSeries object that is used by the mapper. + + \note All data in the series is discarded when it is set to the mapper. When a new series is + specified, the old series is disconnected (preserving its data). +*/ + +/*! + \fn Qt::Orientation QCandlestickModelMapper::orientation() const + Returns the orientation that is used when QCandlestickModelMapper accesses the model. This + determines whether the consecutive values of the set are read from rows (Qt::Horizontal) or from + columns (Qt::Vertical). +*/ + +/*! + \fn void QCandlestickModelMapper::modelReplaced() + \brief Emitted when the model, to which the mapper is connected, has changed. + \sa model +*/ + +/*! + \fn void QCandlestickModelMapper::seriesReplaced() + \brief Emitted when the series to which mapper is connected to has changed. + \sa series +*/ + +/*! + Constructs a model mapper object as a child of \a parent. +*/ +QCandlestickModelMapper::QCandlestickModelMapper(QObject *parent) + : QObject(parent), + d_ptr(new QCandlestickModelMapperPrivate(this)) +{ +} + +void QCandlestickModelMapper::setModel(QAbstractItemModel *model) +{ + Q_D(QCandlestickModelMapper); + + if (d->m_model == model) + return; + + if (d->m_model) + disconnect(d->m_model, 0, d, 0); + + d->m_model = model; + emit modelReplaced(); + + if (!d->m_model) + return; + + d->initializeCandlestickFromModel(); + // connect signals from the model + connect(d->m_model, SIGNAL(modelReset()), d, SLOT(initializeCandlestickFromModel())); + connect(d->m_model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), + d, SLOT(modelDataUpdated(QModelIndex, QModelIndex))); + connect(d->m_model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)), + d, SLOT(modelHeaderDataUpdated(Qt::Orientation, int, int))); + connect(d->m_model, SIGNAL(rowsInserted(QModelIndex, int, int)), + d, SLOT(modelRowsInserted(QModelIndex, int, int))); + connect(d->m_model, SIGNAL(rowsRemoved(QModelIndex, int, int)), + d, SLOT(modelRowsRemoved(QModelIndex, int, int))); + connect(d->m_model, SIGNAL(columnsInserted(QModelIndex, int, int)), + d, SLOT(modelColumnsInserted(QModelIndex, int, int))); + connect(d->m_model, SIGNAL(columnsRemoved(QModelIndex, int, int)), + d, SLOT(modelColumnsRemoved(QModelIndex, int, int))); + connect(d->m_model, SIGNAL(destroyed()), d, SLOT(modelDestroyed())); +} + +QAbstractItemModel *QCandlestickModelMapper::model() const +{ + Q_D(const QCandlestickModelMapper); + + return d->m_model; +} + +void QCandlestickModelMapper::setSeries(QCandlestickSeries *series) +{ + Q_D(QCandlestickModelMapper); + + if (d->m_series == series) + return; + + if (d->m_series) + disconnect(d->m_series, 0, d, 0); + + d->m_series = series; + emit seriesReplaced(); + + if (!d->m_series) + return; + + d->initializeCandlestickFromModel(); + // connect the signals from the series + connect(d->m_series, SIGNAL(candlestickSetsAdded(QList)), + d, SLOT(candlestickSetsAdded(QList))); + connect(d->m_series, SIGNAL(candlestickSetsRemoved(QList)), + d, SLOT(candlestickSetsRemoved(QList))); + connect(d->m_series, SIGNAL(destroyed()), d, SLOT(seriesDestroyed())); +} + +QCandlestickSeries *QCandlestickModelMapper::series() const +{ + Q_D(const QCandlestickModelMapper); + + return d->m_series; +} + +/*! + Sets the row/column of the model that contains the \a timestamp values of the sets in the + series. Default value is -1 (invalid mapping). +*/ +void QCandlestickModelMapper::setTimestamp(int timestamp) +{ + Q_D(QCandlestickModelMapper); + + timestamp = qMax(timestamp, -1); + + if (d->m_timestamp == timestamp) + return; + + d->m_timestamp = timestamp; + emit d->timestampChanged(); + d->initializeCandlestickFromModel(); +} + +/*! + Returns the row/column of the model that contains the timestamp values of the sets in the + series. Default value is -1 (invalid mapping). +*/ +int QCandlestickModelMapper::timestamp() const +{ + Q_D(const QCandlestickModelMapper); + + return d->m_timestamp; +} + +/*! + Sets the row/column of the model that contains the \a open values of the sets in the series. + Default value is -1 (invalid mapping). +*/ +void QCandlestickModelMapper::setOpen(int open) +{ + Q_D(QCandlestickModelMapper); + + open = qMax(open, -1); + + if (d->m_open == open) + return; + + d->m_open = open; + emit d->openChanged(); + d->initializeCandlestickFromModel(); +} + +/*! + Returns the row/column of the model that contains the open values of the sets in the series. + Default value is -1 (invalid mapping). +*/ +int QCandlestickModelMapper::open() const +{ + Q_D(const QCandlestickModelMapper); + + return d->m_open; +} + +/*! + Sets the row/column of the model that contains the \a high values of the sets in the series. + Default value is -1 (invalid mapping). +*/ +void QCandlestickModelMapper::setHigh(int high) +{ + Q_D(QCandlestickModelMapper); + + high = qMax(high, -1); + + if (d->m_high == high) + return; + + d->m_high = high; + emit d->highChanged(); + d->initializeCandlestickFromModel(); +} + +/*! + Returns the row/column of the model that contains the high values of the sets in the series. + Default value is -1 (invalid mapping). +*/ +int QCandlestickModelMapper::high() const +{ + Q_D(const QCandlestickModelMapper); + + return d->m_high; +} + +/*! + Sets the row/column of the model that contains the \a low values of the sets in the series. + Default value is -1 (invalid mapping). +*/ +void QCandlestickModelMapper::setLow(int low) +{ + Q_D(QCandlestickModelMapper); + + low = qMax(low, -1); + + if (d->m_low == low) + return; + + d->m_low = low; + emit d->lowChanged(); + d->initializeCandlestickFromModel(); +} + +/*! + Returns the row/column of the model that contains the low values of the sets in the series. + Default value is -1 (invalid mapping). +*/ +int QCandlestickModelMapper::low() const +{ + Q_D(const QCandlestickModelMapper); + + return d->m_low; +} + +/*! + Sets the row/column of the model that contains the \a close values of the sets in the series. + Default value is -1 (invalid mapping). +*/ +void QCandlestickModelMapper::setClose(int close) +{ + Q_D(QCandlestickModelMapper); + + close = qMax(close, -1); + + if (d->m_close == close) + return; + + d->m_close = close; + emit d->closeChanged(); + d->initializeCandlestickFromModel(); +} + +/*! + Returns the row/column of the model that contains the close values of the sets in the series. + Default value is -1 (invalid mapping). +*/ +int QCandlestickModelMapper::close() const +{ + Q_D(const QCandlestickModelMapper); + + return d->m_close; +} + +/*! + Sets the section of the model that is used as the data source for the first candlestick set. + Parameter \a firstCandlestickSetSection specifies the section of the model. Default value is -1. +*/ +void QCandlestickModelMapper::setFirstCandlestickSetSection(int firstCandlestickSetSection) +{ + Q_D(QCandlestickModelMapper); + + firstCandlestickSetSection = qMax(firstCandlestickSetSection, -1); + + if (d->m_firstCandlestickSetSection == firstCandlestickSetSection) + return; + + d->m_firstCandlestickSetSection = firstCandlestickSetSection; + emit d->firstCandlestickSetSectionChanged(); + d->initializeCandlestickFromModel(); +} + +/*! + Returns the section of the model that is used as the data source for the first candlestick set. + Default value is -1 (invalid mapping). +*/ +int QCandlestickModelMapper::firstCandlestickSetSection() const +{ + Q_D(const QCandlestickModelMapper); + + return d->m_firstCandlestickSetSection; +} + +/*! + Sets the section of the model that is used as the data source for the last candlestick set. + Parameter \a lastCandlestickSetSection specifies the section of the model. Default value is -1. +*/ +void QCandlestickModelMapper::setLastCandlestickSetSection(int lastCandlestickSetSection) +{ + Q_D(QCandlestickModelMapper); + + lastCandlestickSetSection = qMax(lastCandlestickSetSection, -1); + + if (d->m_lastCandlestickSetSection == lastCandlestickSetSection) + return; + + d->m_lastCandlestickSetSection = lastCandlestickSetSection; + emit d->lastCandlestickSetSectionChanged(); + d->initializeCandlestickFromModel(); +} + +/*! + Returns the section of the model that is used as the data source for the last candlestick set. + Default value is -1 (invalid mapping). +*/ +int QCandlestickModelMapper::lastCandlestickSetSection() const +{ + Q_D(const QCandlestickModelMapper); + + return d->m_lastCandlestickSetSection; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +QCandlestickModelMapperPrivate::QCandlestickModelMapperPrivate(QCandlestickModelMapper *q) + : QObject(q), + m_model(nullptr), + m_series(nullptr), + m_timestamp(-1), + m_open(-1), + m_high(-1), + m_low(-1), + m_close(-1), + m_firstCandlestickSetSection(-1), + m_lastCandlestickSetSection(-1), + m_modelSignalsBlock(false), + m_seriesSignalsBlock(false), + q_ptr(q) +{ +} + +void QCandlestickModelMapperPrivate::initializeCandlestickFromModel() +{ + if (!m_model || !m_series) + return; + + blockSeriesSignals(); + // clear current content + m_series->clear(); + m_candlestickSets.clear(); + + // create the initial candlestick sets + QList candlestickSets; + for (int i = m_firstCandlestickSetSection; i <= m_lastCandlestickSetSection; ++i) { + QModelIndex timestampIndex = candlestickModelIndex(i, m_timestamp); + QModelIndex openIndex = candlestickModelIndex(i, m_open); + QModelIndex highIndex = candlestickModelIndex(i, m_high); + QModelIndex lowIndex = candlestickModelIndex(i, m_low); + QModelIndex closeIndex = candlestickModelIndex(i, m_close); + if (timestampIndex.isValid() + && openIndex.isValid() + && highIndex.isValid() + && lowIndex.isValid() + && closeIndex.isValid()) { + QCandlestickSet *set = new QCandlestickSet(); + set->setTimestamp(m_model->data(timestampIndex, Qt::DisplayRole).toReal()); + set->setOpen(m_model->data(openIndex, Qt::DisplayRole).toReal()); + set->setHigh(m_model->data(highIndex, Qt::DisplayRole).toReal()); + set->setLow(m_model->data(lowIndex, Qt::DisplayRole).toReal()); + set->setClose(m_model->data(closeIndex, Qt::DisplayRole).toReal()); + + connect(set, SIGNAL(timestampChanged()), this, SLOT(candlestickSetChanged())); + connect(set, SIGNAL(openChanged()), this, SLOT(candlestickSetChanged())); + connect(set, SIGNAL(highChanged()), this, SLOT(candlestickSetChanged())); + connect(set, SIGNAL(lowChanged()), this, SLOT(candlestickSetChanged())); + connect(set, SIGNAL(closeChanged()), this, SLOT(candlestickSetChanged())); + + candlestickSets.append(set); + } else { + break; + } + } + m_series->append(candlestickSets); + m_candlestickSets.append(candlestickSets); + blockSeriesSignals(false); +} + +void QCandlestickModelMapperPrivate::modelDataUpdated(QModelIndex topLeft, QModelIndex bottomRight) +{ + Q_Q(QCandlestickModelMapper); + + if (!m_model || !m_series) + return; + + if (m_modelSignalsBlock) + return; + + blockSeriesSignals(); + QModelIndex index; + for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { + for (int column = topLeft.column(); column <= bottomRight.column(); ++column) { + index = topLeft.sibling(row, column); + QCandlestickSet *set = candlestickSet(index); + if (set) { + int pos = (q->orientation() == Qt::Vertical) ? row : column; + if (pos == m_timestamp) + set->setTimestamp(m_model->data(index).toReal()); + else if (pos == m_open) + set->setOpen(m_model->data(index).toReal()); + else if (pos == m_high) + set->setHigh(m_model->data(index).toReal()); + else if (pos == m_low) + set->setLow(m_model->data(index).toReal()); + else if (pos == m_close) + set->setClose(m_model->data(index).toReal()); + } + } + } + blockSeriesSignals(false); +} + +void QCandlestickModelMapperPrivate::modelHeaderDataUpdated(Qt::Orientation orientation, int first, + int last) +{ + Q_UNUSED(orientation); + Q_UNUSED(first); + Q_UNUSED(last); +} + +void QCandlestickModelMapperPrivate::modelRowsInserted(QModelIndex parent, int start, int end) +{ + Q_UNUSED(parent) + + Q_Q(QCandlestickModelMapper); + + if (m_modelSignalsBlock) + return; + + blockSeriesSignals(); + if (q->orientation() == Qt::Vertical) + insertData(start, end); + else if (start <= m_firstCandlestickSetSection || start <= m_lastCandlestickSetSection) + initializeCandlestickFromModel(); // if the changes affect the map - reinitialize + blockSeriesSignals(false); +} + +void QCandlestickModelMapperPrivate::modelRowsRemoved(QModelIndex parent, int start, int end) +{ + Q_UNUSED(parent) + + Q_Q(QCandlestickModelMapper); + + if (m_modelSignalsBlock) + return; + + blockSeriesSignals(); + if (q->orientation() == Qt::Vertical) + removeData(start, end); + else if (start <= m_firstCandlestickSetSection || start <= m_lastCandlestickSetSection) + initializeCandlestickFromModel(); // if the changes affect the map - reinitialize + blockSeriesSignals(false); +} + +void QCandlestickModelMapperPrivate::modelColumnsInserted(QModelIndex parent, int start, int end) +{ + Q_UNUSED(parent) + + Q_Q(QCandlestickModelMapper); + + if (m_modelSignalsBlock) + return; + + blockSeriesSignals(); + if (q->orientation() == Qt::Horizontal) + insertData(start, end); + else if (start <= m_firstCandlestickSetSection || start <= m_lastCandlestickSetSection) + initializeCandlestickFromModel(); // if the changes affect the map - reinitialize + blockSeriesSignals(false); +} + +void QCandlestickModelMapperPrivate::modelColumnsRemoved(QModelIndex parent, int start, int end) +{ + Q_UNUSED(parent) + + Q_Q(QCandlestickModelMapper); + + if (m_modelSignalsBlock) + return; + + blockSeriesSignals(); + if (q->orientation() == Qt::Horizontal) + removeData(start, end); + else if (start <= m_firstCandlestickSetSection || start <= m_lastCandlestickSetSection) + initializeCandlestickFromModel(); // if the changes affect the map - reinitialize + blockSeriesSignals(false); +} + +void QCandlestickModelMapperPrivate::modelDestroyed() +{ + m_model = 0; +} + +void QCandlestickModelMapperPrivate::candlestickSetsAdded(const QList &sets) +{ + Q_Q(QCandlestickModelMapper); + + if (m_seriesSignalsBlock) + return; + + if (sets.isEmpty()) + return; + + int firstIndex = m_series->candlestickSets().indexOf(sets.at(0)); + if (firstIndex == -1) + return; + + m_lastCandlestickSetSection += sets.count(); + + blockModelSignals(); + if (q->orientation() == Qt::Vertical) + m_model->insertColumns(firstIndex + m_firstCandlestickSetSection, sets.count()); + else + m_model->insertRows(firstIndex + m_firstCandlestickSetSection, sets.count()); + + for (int i = 0; i < sets.count(); ++i) { + int section = i + firstIndex + m_firstCandlestickSetSection; + m_model->setData(candlestickModelIndex(section, m_timestamp), sets.at(i)->timestamp()); + m_model->setData(candlestickModelIndex(section, m_open), sets.at(i)->open()); + m_model->setData(candlestickModelIndex(section, m_high), sets.at(i)->high()); + m_model->setData(candlestickModelIndex(section, m_low), sets.at(i)->low()); + m_model->setData(candlestickModelIndex(section, m_close), sets.at(i)->close()); + } + blockModelSignals(false); + initializeCandlestickFromModel(); +} + +void QCandlestickModelMapperPrivate::candlestickSetsRemoved(const QList &sets) +{ + Q_Q(QCandlestickModelMapper); + + if (m_seriesSignalsBlock) + return; + + if (sets.isEmpty()) + return; + + int firstIndex = m_candlestickSets.indexOf(sets.at(0)); + if (firstIndex == -1) + return; + + m_lastCandlestickSetSection -= sets.count(); + + for (int i = firstIndex + sets.count() - 1; i >= firstIndex; --i) + m_candlestickSets.removeAt(i); + + blockModelSignals(); + if (q->orientation() == Qt::Vertical) + m_model->removeColumns(firstIndex + m_firstCandlestickSetSection, sets.count()); + else + m_model->removeRows(firstIndex + m_firstCandlestickSetSection, sets.count()); + blockModelSignals(false); + initializeCandlestickFromModel(); +} + +void QCandlestickModelMapperPrivate::candlestickSetChanged() +{ + if (m_seriesSignalsBlock) + return; + + QCandlestickSet *set = qobject_cast(QObject::sender()); + if (!set) + return; + + int section = m_series->candlestickSets().indexOf(set); + if (section < 0) + return; + + section += m_firstCandlestickSetSection; + + blockModelSignals(); + m_model->setData(candlestickModelIndex(section, m_timestamp), set->timestamp()); + m_model->setData(candlestickModelIndex(section, m_open), set->open()); + m_model->setData(candlestickModelIndex(section, m_high), set->high()); + m_model->setData(candlestickModelIndex(section, m_low), set->low()); + m_model->setData(candlestickModelIndex(section, m_close), set->close()); + blockModelSignals(false); +} + +void QCandlestickModelMapperPrivate::seriesDestroyed() +{ + m_series = 0; +} + +QCandlestickSet *QCandlestickModelMapperPrivate::candlestickSet(QModelIndex index) +{ + Q_Q(QCandlestickModelMapper); + + if (!index.isValid()) + return 0; + + int section = (q->orientation() == Qt::Vertical) ? index.column() : index.row(); + int pos = (q->orientation() == Qt::Vertical) ? index.row() : index.column(); + + if (section < m_firstCandlestickSetSection || section > m_lastCandlestickSetSection) + return 0; // This part of model has not been mapped to any candlestick set. + + if (pos != m_timestamp && pos != m_open && pos != m_high && pos != m_low && pos != m_close) + return 0; // This part of model has not been mapped to any candlestick set. + + return m_series->candlestickSets().at(section - m_firstCandlestickSetSection); +} + +QModelIndex QCandlestickModelMapperPrivate::candlestickModelIndex(int section, int pos) +{ + Q_Q(QCandlestickModelMapper); + + if (section < m_firstCandlestickSetSection || section > m_lastCandlestickSetSection) + return QModelIndex(); // invalid + + if (pos != m_timestamp && pos != m_open && pos != m_high && pos != m_low && pos != m_close) + return QModelIndex(); // invalid + + if (q->orientation() == Qt::Vertical) + return m_model->index(pos, section); + else + return m_model->index(section, pos); +} + +void QCandlestickModelMapperPrivate::insertData(int start, int end) +{ + Q_UNUSED(start) + Q_UNUSED(end) + + // Currently candlestickchart needs to be fully recalculated when change is made. + initializeCandlestickFromModel(); +} + +void QCandlestickModelMapperPrivate::removeData(int start, int end) +{ + Q_UNUSED(start) + Q_UNUSED(end) + + // Currently candlestickchart needs to be fully recalculated when change is made. + initializeCandlestickFromModel(); +} + +void QCandlestickModelMapperPrivate::blockModelSignals(bool block) +{ + m_modelSignalsBlock = block; +} + +void QCandlestickModelMapperPrivate::blockSeriesSignals(bool block) +{ + m_seriesSignalsBlock = block; +} + +#include "moc_qcandlestickmodelmapper.cpp" +#include "moc_qcandlestickmodelmapper_p.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/candlestickchart/qcandlestickmodelmapper.h b/src/charts/candlestickchart/qcandlestickmodelmapper.h new file mode 100644 index 0000000..217468c --- /dev/null +++ b/src/charts/candlestickchart/qcandlestickmodelmapper.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCANDLESTICKMODELMAPPER_H +#define QCANDLESTICKMODELMAPPER_H + +#include +#include + +QT_BEGIN_NAMESPACE +class QAbstractItemModel; +QT_END_NAMESPACE + +QT_CHARTS_BEGIN_NAMESPACE + +class QCandlestickModelMapperPrivate; +class QCandlestickSeries; + +class QT_CHARTS_EXPORT QCandlestickModelMapper : public QObject +{ + Q_OBJECT + Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelReplaced) + Q_PROPERTY(QCandlestickSeries *series READ series WRITE setSeries NOTIFY seriesReplaced) + +public: + explicit QCandlestickModelMapper(QObject *parent = nullptr); + + void setModel(QAbstractItemModel *model); + QAbstractItemModel *model() const; + + void setSeries(QCandlestickSeries *series); + QCandlestickSeries *series() const; + + virtual Qt::Orientation orientation() const = 0; + +Q_SIGNALS: + void modelReplaced(); + void seriesReplaced(); + +protected: + void setTimestamp(int timestamp); + int timestamp() const; + + void setOpen(int open); + int open() const; + + void setHigh(int high); + int high() const; + + void setLow(int low); + int low() const; + + void setClose(int close); + int close() const; + + void setFirstCandlestickSetSection(int firstCandlestickSetSection); + int firstCandlestickSetSection() const; + + void setLastCandlestickSetSection(int lastCandlestickSetSection); + int lastCandlestickSetSection() const; + +protected: + QCandlestickModelMapperPrivate * const d_ptr; + Q_DECLARE_PRIVATE(QCandlestickModelMapper) +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QCANDLESTICKMODELMAPPER_H diff --git a/src/charts/candlestickchart/qcandlestickmodelmapper_p.h b/src/charts/candlestickchart/qcandlestickmodelmapper_p.h new file mode 100644 index 0000000..df84e3f --- /dev/null +++ b/src/charts/candlestickchart/qcandlestickmodelmapper_p.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef QCANDLESTICKMODELMAPPER_P_H +#define QCANDLESTICKMODELMAPPER_P_H + +#include +#include + +QT_BEGIN_NAMESPACE +class QModelIndex; +QT_END_NAMESPACE + +QT_CHARTS_BEGIN_NAMESPACE + +class QCandlestickSet; + +class QCandlestickModelMapperPrivate : public QObject +{ + Q_OBJECT + +public: + explicit QCandlestickModelMapperPrivate(QCandlestickModelMapper *q); + +Q_SIGNALS: + void timestampChanged(); + void openChanged(); + void highChanged(); + void lowChanged(); + void closeChanged(); + void firstCandlestickSetSectionChanged(); + void lastCandlestickSetSectionChanged(); + +private Q_SLOTS: + void initializeCandlestickFromModel(); + + // for the model + void modelDataUpdated(QModelIndex topLeft, QModelIndex bottomRight); + void modelHeaderDataUpdated(Qt::Orientation orientation, int first, int last); + void modelRowsInserted(QModelIndex parent, int start, int end); + void modelRowsRemoved(QModelIndex parent, int start, int end); + void modelColumnsInserted(QModelIndex parent, int start, int end); + void modelColumnsRemoved(QModelIndex parent, int start, int end); + void modelDestroyed(); + + // for the series + void candlestickSetsAdded(const QList &sets); + void candlestickSetsRemoved(const QList &sets); + void candlestickSetChanged(); + void seriesDestroyed(); + +private: + QCandlestickSet *candlestickSet(QModelIndex index); + QModelIndex candlestickModelIndex(int section, int pos); + void insertData(int start, int end); + void removeData(int start, int end); + void blockModelSignals(bool block = true); + void blockSeriesSignals(bool block = true); + +private: + QAbstractItemModel *m_model; + QCandlestickSeries *m_series; + int m_timestamp; + int m_open; + int m_high; + int m_low; + int m_close; + int m_firstCandlestickSetSection; + int m_lastCandlestickSetSection; + QList m_candlestickSets; + bool m_modelSignalsBlock; + bool m_seriesSignalsBlock; + +private: + QCandlestickModelMapper *q_ptr; + Q_DECLARE_PUBLIC(QCandlestickModelMapper) +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QCANDLESTICKMODELMAPPER_P_H diff --git a/src/charts/candlestickchart/qcandlestickseries.cpp b/src/charts/candlestickchart/qcandlestickseries.cpp new file mode 100644 index 0000000..2e04d6c --- /dev/null +++ b/src/charts/candlestickchart/qcandlestickseries.cpp @@ -0,0 +1,1138 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +/*! + \class QCandlestickSeries + \since 5.8 + \inmodule Qt Charts + \brief Series for creating a candlestick chart. + + QCandlestickSeries represents a series of data shown as candlesticks. The purpose of this class + is to act as a container for single candlestick items. Each item is drawn to its own category + when using QBarCategoryAxis. QDateTimeAxis and QValueAxis can be used as alternatives to + QBarCategoryAxis. In this case, each candlestick item is drawn according to its timestamp value. + + \note The timestamps must be unique within a QCandlestickSeries. When using QBarCategoryAxis, + only the first one of the candlestick items sharing a timestamp is drawn. If the chart includes + multiple instances of QCandlestickSeries, items from different series sharing a timestamp are + drawn to the same category. When using QValueAxis or QDateTimeAxis, candlestick items sharing a + timestamp will overlap each other. + + See the \l {Candlestick Chart Example} {candlestick chart example} to learn how to create + a candlestick chart. + \image examples_candlestickchart.png + + \sa QCandlestickSet, QBarCategoryAxis, QDateTimeAxis, QValueAxis +*/ + +/*! + \qmltype CandlestickSeries + \since 2.2 + \instantiates QCandlestickSeries + \inqmlmodule QtCharts + \inherits AbstractSeries + \brief Series for creating a candlestick chart. + + CandlestickSeries represents a series of data shown as candlesticks. The purpose of this class + is to act as a container for single candlestick items. Each item is drawn to its own category + when using BarCategoryAxis. DateTimeAxis and ValueAxis can be used as an alternative to + BarCategoryAxis. In this case each candlestick item is drawn according to its timestamp value. + + \note The timestamps must be unique within a CandlestickSeries. When using BarCategoryAxis, only + the first one of the candlestick items sharing a timestamp is drawn. If the chart includes + multiple instances of CandlestickSeries, items from different series sharing a timestamp are + drawn to the same category. When using ValueAxis or DateTimeAxis, candlestick items sharing a + timestamp will overlap each other. + + The following QML shows how to create a simple candlestick chart: + \code + import QtQuick 2.5 + import QtCharts 2.2 + + ChartView { + title: "Candlestick Series" + width: 400 + height: 300 + + CandlestickSeries { + name: "Acme Ltd." + increasingColor: "green" + decreasingColor: "red" + + CandlestickSet { timestamp: 1435708800000; open: 690; high: 694; low: 599; close: 660 } + CandlestickSet { timestamp: 1435795200000; open: 669; high: 669; low: 669; close: 669 } + CandlestickSet { timestamp: 1436140800000; open: 485; high: 623; low: 485; close: 600 } + CandlestickSet { timestamp: 1436227200000; open: 589; high: 615; low: 377; close: 569 } + CandlestickSet { timestamp: 1436313600000; open: 464; high: 464; low: 254; close: 254 } + } + } + \endcode + + \beginfloatleft + \image examples_qmlcandlestick.png + \endfloat + \clearfloat + + \sa CandlestickSet, BarCategoryAxis, DateTimeAxis, ValueAxis +*/ + +/*! + \property QCandlestickSeries::count + \brief The count of sets in series. +*/ + +/*! + \qmlproperty int CandlestickSeries::count + The count of sets in series. +*/ + +/*! + \property QCandlestickSeries::maximumColumnWidth + \brief The maximum width of the candlestick items in pixels. Setting a negative value means + there is no maximum width. All negative values are converted to -1.0. +*/ + +/*! + \qmlproperty qreal CandlestickSeries::maximumColumnWidth + \brief The maximum width of the candlestick items in pixels. Setting a negative value means + there is no maximum width. All negative values are converted to -1.0. +*/ + +/*! + \property QCandlestickSeries::minimumColumnWidth + \brief The minimum width of the candlestick items in pixels. Setting a negative value means + there is no minimum width. All negative values are converted to -1.0. +*/ + +/*! + \qmlproperty qreal CandlestickSeries::minimumColumnWidth + \brief The minimum width of the candlestick items in pixels. Setting a negative value means + there is no minimum width. All negative values are converted to -1.0. +*/ + +/*! + \property QCandlestickSeries::bodyWidth + \brief The width of the candlestick items. + + The value signifies the relative width of the candlestick item inside its own slot, in the range + 0.0 to 1.0. Values outside this range are clamped to 0.0 or 1.0. +*/ + +/*! + \qmlproperty qreal CandlestickSeries::bodyWidth + \brief The width of the candlestick items. + + The value signifies the relative width of the candlestick item inside its own slot, in the range + 0.0 to 1.0. Values outside this range are clamped to 0.0 or 1.0. +*/ + +/*! + \property QCandlestickSeries::bodyOutlineVisible + \brief The visibility of the candlestick body outlines. +*/ + +/*! + \qmlproperty bool CandlestickSeries::bodyOutlineVisible + \brief The visibility of the candlestick body outlines. +*/ + +/*! + \property QCandlestickSeries::capsWidth + \brief The width of the caps. + + The value signifies the relative width of the caps inside its own candlestick, in the range 0.0 + to 1.0. Values outside this range are clamped to 0.0 or 1.0. +*/ + +/*! + \qmlproperty qreal CandlestickSeries::capsWidth + \brief The width of the caps. + + The value signifies the relative width of the caps inside its own candlestick, in the range 0.0 + to 1.0. Values outside this range are clamped to 0.0 or 1.0. +*/ + +/*! + \property QCandlestickSeries::capsVisible + \brief The visibility of the caps. +*/ + +/*! + \qmlproperty bool CandlestickSeries::capsVisible + \brief The visibility of the caps. +*/ + +/*! + \property QCandlestickSeries::increasingColor + \brief The color of the increasing candlestick item body. Candlestick is \e increasing when its + close value is higher than the open value. By default this property is set to brush color. + Default color is used also when the property is set to an invalid color value. +*/ + +/*! + \qmlproperty QColor CandlestickSeries::increasingColor + \brief The color of the increasing candlestick item body. Candlestick is \e increasing when its + close value is higher than the open value. By default this property is set to brush color. + Default color is used also when the property is set to an invalid color value. +*/ + +/*! + \property QCandlestickSeries::decreasingColor + \brief The color of the decreasing candlestick item body. Candlestick is \e decreasing when its + open value is higher than the close value. By default this property is set to brush color with + alpha channel set to 128. Default color is used also when the property is set to an invalid + color value. +*/ + +/*! + \qmlproperty QColor CandlestickSeries::decreasingColor + \brief The color of the decreasing candlestick item body. Candlestick is \e decreasing when its + open value is higher than the close value. By default this property is set to brush color with + alpha channel set to 128. Default color is used also when the property is set to an invalid + color value. +*/ + +/*! + \property QCandlestickSeries::brush + \brief The brush of the candlestick items. +*/ + +/*! + \property QCandlestickSeries::pen + \brief The pen of the candlestick items. +*/ + +/*! + \qmlproperty QString CandlestickSeries::brushFilename + \brief The name of the file used as a brush for the series. +*/ + +/*! + \fn void QCandlestickSeries::clicked(QCandlestickSet *set) + \brief Emitted when a \a set is clicked (pressed and released) on the chart. +*/ + +/*! + \qmlsignal CandlestickSeries::clicked(CandlestickSet set) + \brief Emitted when a \a set is clicked (pressed and released) on the chart. + + The corresponding signal handler is \c {onClicked}. +*/ + +/*! + \fn void QCandlestickSeries::hovered(bool status, QCandlestickSet *set) + \brief Emitted when there is change in hover \a status over the \a set. +*/ + +/*! + \qmlsignal CandlestickSeries::hovered(bool status, CandlestickSet set) + \brief Emitted when there is change in hover \a status over the \a set. + + The corresponding signal handler is \c {onHovered}. +*/ + +/*! + \fn void QCandlestickSeries::pressed(QCandlestickSet *set) + \brief Emitted when a \a set is pressed on the chart. +*/ + +/*! + \qmlsignal CandlestickSeries::pressed(CandlestickSet set) + \brief Emitted when a \a set is pressed on the chart. + + The corresponding signal handler is \c {onPressed}. +*/ + +/*! + \fn void QCandlestickSeries::released(QCandlestickSet *set) + \brief Emitted when a \a set is released on the chart. +*/ + +/*! + \qmlsignal CandlestickSeries::released(CandlestickSet set) + \brief Emitted when a \a set is released on the chart. + + The corresponding signal handler is \c {onReleased}. +*/ + +/*! + \fn void QCandlestickSeries::doubleClicked(QCandlestickSet *set) + \brief Emitted when a \a set is double-clicked on the chart. +*/ + +/*! + \qmlsignal CandlestickSeries::doubleClicked(CandlestickSet set) + \brief Emitted when a \a set is double-clicked on the chart. + + The corresponding signal handler is \c {onDoubleClicked}. +*/ + +/*! + \fn void QCandlestickSeries::candlestickSetsAdded(const QList &sets) + \brief Emitted when new \a sets are added to the series. +*/ + +/*! + \qmlsignal CandlestickSeries::candlestickSetsAdded(list sets) + \brief Emitted when new \a sets are added to the series. + + The corresponding signal handler is \c {onCandlestickSetsAdded}. +*/ + +/*! + \fn void QCandlestickSeries::candlestickSetsRemoved(const QList &sets) + \brief Emitted when \a sets are removed from the series. +*/ + +/*! + \qmlsignal CandlestickSeries::candlestickSetsRemoved(list sets) + \brief Emitted when \a sets are removed from the series. + + The corresponding signal handler is \c {onCandlestickSetsRemoved}. +*/ + +/*! + \fn void QCandlestickSeries::countChanged() + \brief Emitted when there is a change in the count of candlestick items in the series. + \sa count +*/ + +/*! + \qmlsignal CandlestickSeries::countChanged() + \brief Emitted when there is a change in the count of candlestick items in the series. + \sa count + + The corresponding signal handler is \c {onCountChanged}. +*/ + +/*! + \fn void QCandlestickSeries::maximumColumnWidthChanged() + \brief Emitted when there is a change in the maximum column width of candlestick items. + \sa maximumColumnWidth +*/ + +/*! + \qmlsignal CandlestickSeries::maximumColumnWidthChanged() + \brief Emitted when there is a change in the maximum column width of candlestick items. + \sa maximumColumnWidth + + The corresponding signal handler is \c {onMaximumColumnWidthChanged}. +*/ + +/*! + \fn void QCandlestickSeries::minimumColumnWidthChanged() + \brief Emitted when there is a change in the minimum column width of candlestick items. + \sa minimumColumnWidth +*/ + +/*! + \qmlsignal CandlestickSeries::minimumColumnWidthChanged() + \brief Emitted when there is a change in the minimum column width of candlestick items. + \sa minimumColumnWidth + + The corresponding signal handler is \c {onMinimumColumnWidthChanged}. +*/ + +/*! + \fn void QCandlestickSeries::bodyWidthChanged() + \brief Emitted when the candlestick item width is changed. + \sa bodyWidth +*/ + +/*! + \qmlsignal CandlestickSeries::bodyWidthChanged() + \brief Emitted when the candlestick item width is changed. + \sa bodyWidth + + The corresponding signal handler is \c {onBodyWidthChanged}. +*/ + +/*! + \fn void QCandlestickSeries::bodyOutlineVisibilityChanged() + \brief Emitted when the visibility of the candlestick item body outline is changed. + \sa bodyOutlineVisible +*/ + +/*! + \qmlsignal CandlestickSeries::bodyOutlineVisibilityChanged() + \brief Emitted when the visibility of the candlestick item body outline is changed. + \sa bodyOutlineVisible + + The corresponding signal handler is \c {onBodyOutlineVisibilityChanged}. +*/ + +/*! + \fn void QCandlestickSeries::capsWidthChanged() + \brief Emitted when the candlestick item caps width is changed. + \sa capsWidth +*/ + +/*! + \qmlsignal CandlestickSeries::capsWidthChanged() + \brief Emitted when the candlestick item caps width is changed. + \sa capsWidth + + The corresponding signal handler is \c {onCapsWidthChanged}. +*/ + +/*! + \fn void QCandlestickSeries::capsVisibilityChanged() + \brief Emitted when the visibility of the candlestick item caps is changed. + \sa capsVisible +*/ + +/*! + \qmlsignal CandlestickSeries::capsVisibilityChanged() + \brief Emitted when the visibility of the candlestick item caps is changed. + \sa capsVisible + + The corresponding signal handler is \c {onCapsVisibilityChanged}. +*/ + +/*! + \fn void QCandlestickSeries::increasingColorChanged() + \brief Emitted when the candlestick item increasing color is changed. + \sa increasingColor +*/ + +/*! + \qmlsignal CandlestickSeries::increasingColorChanged() + \brief Emitted when the candlestick item increasing color is changed. + \sa increasingColor + + The corresponding signal handler is \c {onIncreasingColorChanged}. +*/ + +/*! + \fn void QCandlestickSeries::decreasingColorChanged() + \brief Emitted when the candlestick item decreasing color is changed. + \sa decreasingColor +*/ + +/*! + \qmlsignal CandlestickSeries::decreasingColorChanged() + \brief Emitted when the candlestick item decreasing color is changed. + \sa decreasingColor + + The corresponding signal handler is \c {onDecreasingColorChanged}. +*/ + +/*! + \fn void QCandlestickSeries::brushChanged() + \brief Emitted when the candlestick item brush is changed. + \sa brush +*/ + +/*! + \fn void QCandlestickSeries::penChanged() + \brief Emitted when the candlestick item pen is changed. + \sa pen +*/ + +/*! + Constructs an empty QCandlestickSeries. The \a parent is optional. +*/ +QCandlestickSeries::QCandlestickSeries(QObject *parent) + : QAbstractSeries(*new QCandlestickSeriesPrivate(this), parent) +{ +} + +/*! + Destroys the series. Removes the series from the chart. +*/ +QCandlestickSeries::~QCandlestickSeries() +{ + Q_D(QCandlestickSeries); + if (d->m_chart) + d->m_chart->removeSeries(this); +} + +/*! + Adds a single set to the series. Takes ownership of the \a set. If the set is \e null or is + already in the series, it won't be appended. + Returns \c true if appending succeeded, \c false otherwise. +*/ +bool QCandlestickSeries::append(QCandlestickSet *set) +{ + QList sets; + sets.append(set); + + return append(sets); +} + +/*! + Removes a single set from the series. + Returns \c true if the \a set is successfully deleted, \c false otherwise. +*/ +bool QCandlestickSeries::remove(QCandlestickSet *set) +{ + QList sets; + sets.append(set); + + return remove(sets); +} + +/*! + Adds a list of sets to the series. Takes ownership of the \a sets. If any of the sets are + \e null, already appended to the series, or the list contains duplicated sets, nothing is + appended. + Returns \c true if all sets were appended successfully, \c false otherwise. +*/ +bool QCandlestickSeries::append(const QList &sets) +{ + Q_D(QCandlestickSeries); + + bool success = d->append(sets); + if (success) { + emit candlestickSetsAdded(sets); + emit countChanged(); + } + + return success; +} + +/*! + Removes a list of sets from the series. If any of the \a sets are \e null, already removed from + the series, or the list contains duplicated sets, nothing is removed. + Returns \c true if all sets were removed successfully, \c false otherwise. +*/ +bool QCandlestickSeries::remove(const QList &sets) +{ + Q_D(QCandlestickSeries); + + bool success = d->remove(sets); + if (success) { + emit candlestickSetsRemoved(sets); + emit countChanged(); + foreach (QCandlestickSet *set, sets) + set->deleteLater(); + } + + return success; +} + +/*! + Inserts a set to the series at \a index position. Takes ownership of the \a set. If the set is + \e null or already in the series, it won't be appended. + Returns \c true if inserting succeeded, \c false otherwise. +*/ +bool QCandlestickSeries::insert(int index, QCandlestickSet *set) +{ + Q_D(QCandlestickSeries); + + bool success = d->insert(index, set); + if (success) { + QList sets; + sets.append(set); + emit candlestickSetsAdded(sets); + emit countChanged(); + } + + return success; +} + +/*! + Takes a single \a set from the series. Does not delete the set object. + Returns \c true if take was successful, \c false otherwise. + \note The series remains as the set's parent object. You must set the parent object to take full + ownership. +*/ +bool QCandlestickSeries::take(QCandlestickSet *set) +{ + Q_D(QCandlestickSeries); + + QList sets; + sets.append(set); + + bool success = d->remove(sets); + if (success) { + emit candlestickSetsRemoved(sets); + emit countChanged(); + } + + return success; +} + +/*! + Removes all sets from the series, and deletes them. +*/ +void QCandlestickSeries::clear() +{ + Q_D(QCandlestickSeries); + + QList sets = candlestickSets(); + + bool success = d->remove(sets); + if (success) { + emit candlestickSetsRemoved(sets); + emit countChanged(); + foreach (QCandlestickSet *set, sets) + set->deleteLater(); + } +} + +/*! + Returns the list of sets in the series. Ownership of the sets is unchanged. + */ +QList QCandlestickSeries::candlestickSets() const +{ + Q_D(const QCandlestickSeries); + + return d->m_candlestickSets; +} + +/*! + Returns the number of the sets in the series. +*/ +int QCandlestickSeries::count() const +{ + return candlestickSets().count(); +} + +/*! + Returns the type of the series (QAbstractSeries::SeriesTypeCandlestick). +*/ +QAbstractSeries::SeriesType QCandlestickSeries::type() const +{ + return QAbstractSeries::SeriesTypeCandlestick; +} + +void QCandlestickSeries::setMaximumColumnWidth(qreal maximumColumnWidth) +{ + Q_D(QCandlestickSeries); + + if (maximumColumnWidth < 0.0 && maximumColumnWidth != -1.0) + maximumColumnWidth = -1.0; + + if (d->m_maximumColumnWidth == maximumColumnWidth) + return; + + d->m_maximumColumnWidth = maximumColumnWidth; + + emit d->updatedLayout(); + emit maximumColumnWidthChanged(); +} + +qreal QCandlestickSeries::maximumColumnWidth() const +{ + Q_D(const QCandlestickSeries); + + return d->m_maximumColumnWidth; +} + +void QCandlestickSeries::setMinimumColumnWidth(qreal minimumColumnWidth) +{ + Q_D(QCandlestickSeries); + + if (minimumColumnWidth < 0.0 && minimumColumnWidth != -1.0) + minimumColumnWidth = -1.0; + + if (d->m_minimumColumnWidth == minimumColumnWidth) + return; + + d->m_minimumColumnWidth = minimumColumnWidth; + + d->updatedLayout(); + emit minimumColumnWidthChanged(); +} + +qreal QCandlestickSeries::minimumColumnWidth() const +{ + Q_D(const QCandlestickSeries); + + return d->m_minimumColumnWidth; +} + +void QCandlestickSeries::setBodyWidth(qreal bodyWidth) +{ + Q_D(QCandlestickSeries); + + if (bodyWidth < 0.0) + bodyWidth = 0.0; + else if (bodyWidth > 1.0) + bodyWidth = 1.0; + + if (d->m_bodyWidth == bodyWidth) + return; + + d->m_bodyWidth = bodyWidth; + + emit d->updatedLayout(); + emit bodyWidthChanged(); +} + +qreal QCandlestickSeries::bodyWidth() const +{ + Q_D(const QCandlestickSeries); + + return d->m_bodyWidth; +} + +void QCandlestickSeries::setBodyOutlineVisible(bool bodyOutlineVisible) +{ + Q_D(QCandlestickSeries); + + if (d->m_bodyOutlineVisible == bodyOutlineVisible) + return; + + d->m_bodyOutlineVisible = bodyOutlineVisible; + + emit d->updated(); + emit bodyOutlineVisibilityChanged(); +} + +bool QCandlestickSeries::bodyOutlineVisible() const +{ + Q_D(const QCandlestickSeries); + + return d->m_bodyOutlineVisible; +} + +void QCandlestickSeries::setCapsWidth(qreal capsWidth) +{ + Q_D(QCandlestickSeries); + + if (capsWidth < 0.0) + capsWidth = 0.0; + else if (capsWidth > 1.0) + capsWidth = 1.0; + + if (d->m_capsWidth == capsWidth) + return; + + d->m_capsWidth = capsWidth; + + emit d->updatedLayout(); + emit capsWidthChanged(); +} + +qreal QCandlestickSeries::capsWidth() const +{ + Q_D(const QCandlestickSeries); + + return d->m_capsWidth; +} + +void QCandlestickSeries::setCapsVisible(bool capsVisible) +{ + Q_D(QCandlestickSeries); + + if (d->m_capsVisible == capsVisible) + return; + + d->m_capsVisible = capsVisible; + + emit d->updated(); + emit capsVisibilityChanged(); +} + +bool QCandlestickSeries::capsVisible() const +{ + Q_D(const QCandlestickSeries); + + return d->m_capsVisible; +} + +void QCandlestickSeries::setIncreasingColor(const QColor &increasingColor) +{ + Q_D(QCandlestickSeries); + + QColor color; + if (increasingColor.isValid()) { + color = increasingColor; + d->m_customIncreasingColor = true; + } else { + color = d->m_brush.color(); + color.setAlpha(128); + d->m_customIncreasingColor = false; + } + + if (d->m_increasingColor == color) + return; + + d->m_increasingColor = color; + + emit d->updated(); + emit increasingColorChanged(); +} + +QColor QCandlestickSeries::increasingColor() const +{ + Q_D(const QCandlestickSeries); + + return d->m_increasingColor; +} + +void QCandlestickSeries::setDecreasingColor(const QColor &decreasingColor) +{ + Q_D(QCandlestickSeries); + + QColor color; + if (decreasingColor.isValid()) { + color = decreasingColor; + d->m_customDecreasingColor = true; + } else { + color = d->m_brush.color(); + d->m_customDecreasingColor = false; + } + + if (d->m_decreasingColor == color) + return; + + d->m_decreasingColor = color; + + emit d->updated(); + emit decreasingColorChanged(); +} + +QColor QCandlestickSeries::decreasingColor() const +{ + Q_D(const QCandlestickSeries); + + return d->m_decreasingColor; +} + +void QCandlestickSeries::setBrush(const QBrush &brush) +{ + Q_D(QCandlestickSeries); + + if (d->m_brush == brush) + return; + + d->m_brush = brush; + if (!d->m_customIncreasingColor) { + QColor color = d->m_brush.color(); + color.setAlpha(128); + if (d->m_increasingColor != color) { + d->m_increasingColor = color; + emit increasingColorChanged(); + } + } + if (!d->m_customDecreasingColor && d->m_decreasingColor != d->m_brush.color()) { + d->m_decreasingColor = d->m_brush.color(); + emit decreasingColorChanged(); + } + + emit d->updated(); + emit brushChanged(); +} + +QBrush QCandlestickSeries::brush() const +{ + Q_D(const QCandlestickSeries); + + return d->m_brush; +} + +void QCandlestickSeries::setPen(const QPen &pen) +{ + Q_D(QCandlestickSeries); + + if (d->m_pen == pen) + return; + + d->m_pen = pen; + + emit d->updated(); + emit penChanged(); +} + +QPen QCandlestickSeries::pen() const +{ + Q_D(const QCandlestickSeries); + + return d->m_pen; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +QCandlestickSeriesPrivate::QCandlestickSeriesPrivate(QCandlestickSeries *q) + : QAbstractSeriesPrivate(q), + m_maximumColumnWidth(-1.0), + m_minimumColumnWidth(5.0), + m_bodyWidth(0.5), + m_bodyOutlineVisible(true), + m_capsWidth(0.5), + m_capsVisible(false), + m_increasingColor(QColor(Qt::transparent)), + m_decreasingColor(QChartPrivate::defaultBrush().color()), + m_customIncreasingColor(false), + m_customDecreasingColor(false), + m_brush(QChartPrivate::defaultBrush()), + m_pen(QChartPrivate::defaultPen()), + m_animation(nullptr) +{ +} + +QCandlestickSeriesPrivate::~QCandlestickSeriesPrivate() +{ + disconnect(this, 0, 0, 0); +} + +void QCandlestickSeriesPrivate::initializeDomain() +{ + qreal minX(domain()->minX()); + qreal maxX(domain()->maxX()); + qreal minY(domain()->minY()); + qreal maxY(domain()->maxY()); + + if (m_candlestickSets.count()) { + QCandlestickSet *set = m_candlestickSets.first(); + minX = set->timestamp(); + maxX = set->timestamp(); + minY = set->low(); + maxY = set->high(); + for (int i = 1; i < m_candlestickSets.count(); ++i) { + set = m_candlestickSets.at(i); + minX = qMin(minX, qreal(set->timestamp())); + maxX = qMax(maxX, qreal(set->timestamp())); + minY = qMin(minY, set->low()); + maxY = qMax(maxY, set->high()); + } + qreal extra = (maxX - minX) / m_candlestickSets.count() / 2; + minX = minX - extra; + maxX = maxX + extra; + } + + domain()->setRange(minX, maxX, minY, maxY); +} + +void QCandlestickSeriesPrivate::initializeAxes() +{ + foreach (QAbstractAxis* axis, m_axes) { + if (axis->type() == QAbstractAxis::AxisTypeBarCategory) { + if (axis->orientation() == Qt::Horizontal) + populateBarCategories(qobject_cast(axis)); + } + } +} + +void QCandlestickSeriesPrivate::initializeTheme(int index, ChartTheme* theme, bool forced) +{ + Q_Q(QCandlestickSeries); + + if (forced || QChartPrivate::defaultBrush() == m_brush) { + const QList gradients = theme->seriesGradients(); + const QGradient gradient = gradients.at(index % gradients.size()); + const QBrush brush(ChartThemeManager::colorAt(gradient, 0.5)); + q->setBrush(brush); + } + + if (forced || QChartPrivate::defaultPen() == m_pen) { + QPen pen = theme->outlinePen(); + pen.setCosmetic(true); + q->setPen(pen); + } +} + +void QCandlestickSeriesPrivate::initializeGraphics(QGraphicsItem *parent) +{ + Q_Q(QCandlestickSeries); + + CandlestickChartItem *item = new CandlestickChartItem(q, parent); + m_item.reset(item); + QAbstractSeriesPrivate::initializeGraphics(parent); + + if (m_chart) { + connect(m_chart->d_ptr->m_dataset, SIGNAL(seriesAdded(QAbstractSeries *)), + this, SLOT(handleSeriesChange(QAbstractSeries *))); + connect(m_chart->d_ptr->m_dataset, SIGNAL(seriesRemoved(QAbstractSeries *)), + this, SLOT(handleSeriesRemove(QAbstractSeries *))); + + item->handleCandlestickSeriesChange(); + } +} + +void QCandlestickSeriesPrivate::initializeAnimations(QChart::AnimationOptions options, int duration, + QEasingCurve &curve) +{ + CandlestickChartItem *item = static_cast(m_item.data()); + Q_ASSERT(item); + + if (item->animation()) + item->animation()->stopAndDestroyLater(); + + if (options.testFlag(QChart::SeriesAnimations)) + m_animation = new CandlestickAnimation(item, duration, curve); + else + m_animation = nullptr; + item->setAnimation(m_animation); + + QAbstractSeriesPrivate::initializeAnimations(options, duration, curve); +} + +QList QCandlestickSeriesPrivate::createLegendMarkers(QLegend *legend) +{ + Q_Q(QCandlestickSeries); + + QList list; + + return list << new QCandlestickLegendMarker(q, legend); +} + +QAbstractAxis::AxisType QCandlestickSeriesPrivate::defaultAxisType(Qt::Orientation orientation) const +{ + if (orientation == Qt::Horizontal) + return QAbstractAxis::AxisTypeBarCategory; + + if (orientation == Qt::Vertical) + return QAbstractAxis::AxisTypeValue; + + return QAbstractAxis::AxisTypeNoAxis; +} + +QAbstractAxis* QCandlestickSeriesPrivate::createDefaultAxis(Qt::Orientation orientation) const +{ + const QAbstractAxis::AxisType axisType = defaultAxisType(orientation); + + if (axisType == QAbstractAxis::AxisTypeBarCategory) + return new QBarCategoryAxis; + + if (axisType == QAbstractAxis::AxisTypeValue) + return new QValueAxis; + + return 0; // axisType == QAbstractAxis::AxisTypeNoAxis +} + +bool QCandlestickSeriesPrivate::append(const QList &sets) +{ + foreach (QCandlestickSet *set, sets) { + if ((set == 0) || m_candlestickSets.contains(set) || set->d_ptr->m_series) + return false; // Fail if any of the sets is null or is already appended. + if (sets.count(set) != 1) + return false; // Also fail if the same set occurs more than once in the given list. + } + + foreach (QCandlestickSet *set, sets) { + m_candlestickSets.append(set); + connect(set->d_func(), SIGNAL(updatedLayout()), this, SIGNAL(updatedLayout())); + connect(set->d_func(), SIGNAL(updatedCandlestick()), this, SIGNAL(updatedCandlesticks())); + set->d_ptr->m_series = this; + } + + return true; +} + +bool QCandlestickSeriesPrivate::remove(const QList &sets) +{ + if (sets.count() == 0) + return false; + + foreach (QCandlestickSet *set, sets) { + if ((set == 0) || (!m_candlestickSets.contains(set))) + return false; // Fail if any of the sets is null or is not in series. + if (sets.count(set) != 1) + return false; // Also fail if the same set occurs more than once in the given list. + } + + foreach (QCandlestickSet *set, sets) { + set->d_ptr->m_series = nullptr; + m_candlestickSets.removeOne(set); + disconnect(set->d_func(), SIGNAL(updatedLayout()), this, SIGNAL(updatedLayout())); + disconnect(set->d_func(), SIGNAL(updatedCandlestick()),this, SIGNAL(updatedCandlesticks())); + } + + return true; +} + +bool QCandlestickSeriesPrivate::insert(int index, QCandlestickSet *set) +{ + if ((m_candlestickSets.contains(set)) || (set == 0) || set->d_ptr->m_series) + return false; // Fail if set is already in list or set is null. + + m_candlestickSets.insert(index, set); + connect(set->d_func(), SIGNAL(updatedLayout()), this, SIGNAL(updatedLayout())); + connect(set->d_func(), SIGNAL(updatedCandlestick()), this, SIGNAL(updatedCandlesticks())); + set->d_ptr->m_series = this; + + return true; +} + +void QCandlestickSeriesPrivate::handleSeriesChange(QAbstractSeries *series) +{ + Q_UNUSED(series); + + if (m_chart) { + CandlestickChartItem *item = static_cast(m_item.data()); + if (item) + item->handleCandlestickSeriesChange(); + } +} + +void QCandlestickSeriesPrivate::handleSeriesRemove(QAbstractSeries *series) +{ + Q_Q(const QCandlestickSeries); + + QCandlestickSeries *removedSeries = static_cast(series); + + if (q == removedSeries && m_animation) { + m_animation->stopAll(); + disconnect(m_chart->d_ptr->m_dataset, 0, removedSeries->d_func(), 0); + } + + if (q != removedSeries) { + CandlestickChartItem *item = static_cast(m_item.data()); + if (item) + item->handleCandlestickSeriesChange(); + } +} + +void QCandlestickSeriesPrivate::populateBarCategories(QBarCategoryAxis *axis) +{ + if (axis->categories().isEmpty()) { + QStringList categories; + for (int i = 0; i < m_candlestickSets.count(); ++i) { + const qint64 timestamp = qRound64(m_candlestickSets.at(i)->timestamp()); + const QString timestampFormat = m_chart->locale().dateTimeFormat(QLocale::ShortFormat); + categories << QDateTime::fromMSecsSinceEpoch(timestamp).toString(timestampFormat); + } + axis->append(categories); + } +} + +#include "moc_qcandlestickseries.cpp" +#include "moc_qcandlestickseries_p.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/candlestickchart/qcandlestickseries.h b/src/charts/candlestickchart/qcandlestickseries.h new file mode 100644 index 0000000..3dea643 --- /dev/null +++ b/src/charts/candlestickchart/qcandlestickseries.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCANDLESTICKSERIES_H +#define QCANDLESTICKSERIES_H + +#include + +QT_CHARTS_BEGIN_NAMESPACE + +class QCandlestickSeriesPrivate; +class QCandlestickSet; + +class QT_CHARTS_EXPORT QCandlestickSeries : public QAbstractSeries +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(qreal maximumColumnWidth READ maximumColumnWidth WRITE setMaximumColumnWidth NOTIFY maximumColumnWidthChanged) + Q_PROPERTY(qreal minimumColumnWidth READ minimumColumnWidth WRITE setMinimumColumnWidth NOTIFY minimumColumnWidthChanged) + Q_PROPERTY(qreal bodyWidth READ bodyWidth WRITE setBodyWidth NOTIFY bodyWidthChanged) + Q_PROPERTY(bool bodyOutlineVisible READ bodyOutlineVisible WRITE setBodyOutlineVisible NOTIFY bodyOutlineVisibilityChanged) + Q_PROPERTY(qreal capsWidth READ capsWidth WRITE setCapsWidth NOTIFY capsWidthChanged) + Q_PROPERTY(bool capsVisible READ capsVisible WRITE setCapsVisible NOTIFY capsVisibilityChanged) + Q_PROPERTY(QColor increasingColor READ increasingColor WRITE setIncreasingColor NOTIFY increasingColorChanged) + Q_PROPERTY(QColor decreasingColor READ decreasingColor WRITE setDecreasingColor NOTIFY decreasingColorChanged) + Q_PROPERTY(QBrush brush READ brush WRITE setBrush NOTIFY brushChanged) + Q_PROPERTY(QPen pen READ pen WRITE setPen NOTIFY penChanged) + +public: + explicit QCandlestickSeries(QObject *parent = nullptr); + ~QCandlestickSeries(); + + bool append(QCandlestickSet *set); + bool remove(QCandlestickSet *set); + bool append(const QList &sets); + bool remove(const QList &sets); + bool insert(int index, QCandlestickSet *set); + bool take(QCandlestickSet *set); + void clear(); + + QList candlestickSets() const; + int count() const; + + QAbstractSeries::SeriesType type() const; + + void setMaximumColumnWidth(qreal maximumColumnWidth); + qreal maximumColumnWidth() const; + + void setMinimumColumnWidth(qreal minimumColumnWidth); + qreal minimumColumnWidth() const; + + void setBodyWidth(qreal bodyWidth); + qreal bodyWidth() const; + + void setBodyOutlineVisible(bool bodyOutlineVisible); + bool bodyOutlineVisible() const; + + void setCapsWidth(qreal capsWidth); + qreal capsWidth() const; + + void setCapsVisible(bool capsVisible); + bool capsVisible() const; + + void setIncreasingColor(const QColor &increasingColor); + QColor increasingColor() const; + + void setDecreasingColor(const QColor &decreasingColor); + QColor decreasingColor() const; + + void setBrush(const QBrush &brush); + QBrush brush() const; + + void setPen(const QPen &pen); + QPen pen() const; + +Q_SIGNALS: + void clicked(QCandlestickSet *set); + void hovered(bool status, QCandlestickSet *set); + void pressed(QCandlestickSet *set); + void released(QCandlestickSet *set); + void doubleClicked(QCandlestickSet *set); + void candlestickSetsAdded(const QList &sets); + void candlestickSetsRemoved(const QList &sets); + void countChanged(); + void maximumColumnWidthChanged(); + void minimumColumnWidthChanged(); + void bodyWidthChanged(); + void bodyOutlineVisibilityChanged(); + void capsWidthChanged(); + void capsVisibilityChanged(); + void increasingColorChanged(); + void decreasingColorChanged(); + void brushChanged(); + void penChanged(); + +private: + Q_DISABLE_COPY(QCandlestickSeries) + Q_DECLARE_PRIVATE(QCandlestickSeries) + friend class CandlestickChartItem; + friend class QCandlestickLegendMarkerPrivate; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QCANDLESTICKSERIES_H diff --git a/src/charts/candlestickchart/qcandlestickseries_p.h b/src/charts/candlestickchart/qcandlestickseries_p.h new file mode 100644 index 0000000..7ee5010 --- /dev/null +++ b/src/charts/candlestickchart/qcandlestickseries_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef QCANDLESTICKSERIES_P_H +#define QCANDLESTICKSERIES_P_H + +#include + +QT_CHARTS_BEGIN_NAMESPACE + +class CandlestickAnimation; +class QBarCategoryAxis; +class QCandlestickSeries; +class QCandlestickSet; +class QDateTimeAxis; + +class QCandlestickSeriesPrivate : public QAbstractSeriesPrivate +{ + Q_OBJECT + +public: + QCandlestickSeriesPrivate(QCandlestickSeries *q); + ~QCandlestickSeriesPrivate(); + + void initializeDomain(); + void initializeAxes(); + void initializeTheme(int index, ChartTheme* theme, bool forced = false); + void initializeGraphics(QGraphicsItem* parent); + void initializeAnimations(QChart::AnimationOptions options, int duration, QEasingCurve &curve); + + QList createLegendMarkers(QLegend *legend); + + virtual QAbstractAxis::AxisType defaultAxisType(Qt::Orientation orientation) const; + QAbstractAxis *createDefaultAxis(Qt::Orientation orientation) const; + + bool append(const QList &sets); + bool remove(const QList &sets); + bool insert(int index, QCandlestickSet *set); + +Q_SIGNALS: + void clicked(int index, QCandlestickSet *set); + void pressed(int index, QCandlestickSet *set); + void released(int index, QCandlestickSet *set); + void doubleClicked(int index, QCandlestickSet *set); + void updated(); + void updatedLayout(); + void updatedCandlesticks(); + +private Q_SLOTS: + void handleSeriesChange(QAbstractSeries *series); + void handleSeriesRemove(QAbstractSeries *series); + +private: + void populateBarCategories(QBarCategoryAxis *axis); + +protected: + QList m_candlestickSets; + qreal m_maximumColumnWidth; + qreal m_minimumColumnWidth; + qreal m_bodyWidth; + bool m_bodyOutlineVisible; + qreal m_capsWidth; + bool m_capsVisible; + QColor m_increasingColor; + QColor m_decreasingColor; + bool m_customIncreasingColor; + bool m_customDecreasingColor; + QBrush m_brush; + QPen m_pen; + CandlestickAnimation *m_animation; + +private: + Q_DECLARE_PUBLIC(QCandlestickSeries) +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QCANDLESTICKSERIES_P_H diff --git a/src/charts/candlestickchart/qcandlestickset.cpp b/src/charts/candlestickchart/qcandlestickset.cpp new file mode 100644 index 0000000..99779ef --- /dev/null +++ b/src/charts/candlestickchart/qcandlestickset.cpp @@ -0,0 +1,487 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +/*! + \class QCandlestickSet + \since 5.8 + \inmodule Qt Charts + \brief Building block for a candlestick chart. + + QCandlestickSet represents a single candlestick item in a QCandlestickSeries. It takes five + values to create a graphical representation of a candlestick item: \e open, \e high, \e low, + \e close, and \e timestamp. These values can be either passed to a QCandlestickSet constructor, + or set by using setOpen(), setHigh(), setLow(), setClose(), and setTimestamp(). + + \sa QCandlestickSeries +*/ + +/*! + \qmltype CandlestickSet + \since 2.2 + \instantiates QCandlestickSet + \inqmlmodule QtCharts + \brief Building block for a candlestick chart. + + CandlestickSet represents a single candlestick item in a CandlestickSeries. It takes five + values to create a graphical representation of a candlestick item: \l open, \l high, \l low, + \l close, and \l timestamp. + + \sa CandlestickSeries +*/ + +/*! + \property QCandlestickSet::timestamp + \brief The timestamp value of the set. +*/ + +/*! + \qmlproperty qreal CandlestickSet::timestamp + \brief The timestamp value of the set. +*/ + +/*! + \property QCandlestickSet::open + \brief The open value of the set. +*/ + +/*! + \qmlproperty qreal CandlestickSet::open + \brief The open value of the set. +*/ + +/*! + \property QCandlestickSet::high + \brief The high value of the set. +*/ + +/*! + \qmlproperty qreal CandlestickSet::high + \brief The high value of the set. +*/ + +/*! + \property QCandlestickSet::low + \brief The low value of the set. +*/ + +/*! + \qmlproperty qreal CandlestickSet::low + \brief The low value of the set. +*/ + +/*! + \property QCandlestickSet::close + \brief The close value of the set. +*/ + +/*! + \qmlproperty qreal CandlestickSet::close + \brief The close value of the set. +*/ + +/*! + \property QCandlestickSet::brush + \brief The brush used for drawing the candlestick. +*/ + +/*! + \property QCandlestickSet::pen + \brief The pen used for drawing the candlestick. +*/ + +/*! + \qmlproperty QString CandlestickSet::brushFilename + \brief The name of the file used as a brush for the set. +*/ + +/*! + \fn void QCandlestickSet::clicked() + \brief Emitted when the candlestick item is clicked (pressed and released). +*/ + +/*! + \qmlsignal CandlestickSet::clicked() + \brief Emitted when the candlestick item is clicked (pressed and released). + + The corresponding signal handler is \c {onClicked}. +*/ + +/*! + \fn void QCandlestickSet::hovered(bool status) + \brief Emitted when there is change in hover \a status over a candlestick item. + + Parameter \a status indicates whether the mouse has entered (\c true) or left (\c false) the + area of the candlestick item. +*/ + +/*! + \qmlsignal CandlestickSet::hovered(bool status) + \brief Emitted when there is change in hover \a status over a candlestick item. + + Parameter \a status indicates whether the mouse has entered (\c true) or left (\c false) the + area of the candlestick item. + + The corresponding signal handler is \c {onHovered}. +*/ + +/*! + \fn void QCandlestickSet::pressed() + \brief Emitted when there is a press on a candlestick item. +*/ + +/*! + \qmlsignal CandlestickSet::pressed() + \brief Emitted when there is a press on a candlestick item. + + The corresponding signal handler is \c {onPressed}. +*/ + +/*! + \fn void QCandlestickSet::released() + \brief Emitted when there is a release on a candlestick item. +*/ + +/*! + \qmlsignal CandlestickSet::released() + \brief Emitted when there is a release on a candlestick item. + + The corresponding signal handler is \c {onReleased}. +*/ + +/*! + \fn void QCandlestickSet::doubleClicked() + \brief Emitted when there is a double-click on a candlestick item. +*/ + +/*! + \qmlsignal CandlestickSet::doubleClicked() + \brief Emitted when there is a double-click on a candlestick item. + + The corresponding signal handler is \c {onDoubleClicked}. +*/ + +/*! + \fn void QCandlestickSet::timestampChanged() + \brief Emitted when the candlestick item timestamp is changed. + \sa timestamp +*/ + +/*! + \qmlsignal CandlestickSet::timestampChanged() + \brief Emitted when the candlestick item timestamp is changed. + \sa timestamp + + The corresponding signal handler is \c {onTimestampChanged}. +*/ + +/*! + \fn void QCandlestickSet::openChanged() + \brief Emitted when the candlestick item open value is changed. + \sa open +*/ + +/*! + \qmlsignal CandlestickSet::openChanged() + \brief Emitted when the candlestick item open value is changed. + \sa open + + The corresponding signal handler is \c {onOpenChanged}. +*/ + +/*! + \fn void QCandlestickSet::highChanged() + \brief Emitted when the candlestick item high value is changed. + \sa high +*/ + +/*! + \qmlsignal CandlestickSet::highChanged() + \brief Emitted when the candlestick item high value is changed. + \sa high + + The corresponding signal handler is \c {onHighChanged}. +*/ + +/*! + \fn void QCandlestickSet::lowChanged() + \brief Emitted when the candlestick item low value is changed. + \sa low +*/ + +/*! + \qmlsignal CandlestickSet::lowChanged() + \brief Emitted when the candlestick item low value is changed. + \sa low + + The corresponding signal handler is \c {onLowChanged}. +*/ + +/*! + \fn void QCandlestickSet::closeChanged() + \brief Emitted when the candlestick item close value is changed. + \sa close +*/ + +/*! + \qmlsignal CandlestickSet::closeChanged() + \brief Emitted when the candlestick item close value is changed. + \sa close + + The corresponding signal handler is \c {onCloseChanged}. +*/ + +/*! + \fn void QCandlestickSet::brushChanged() + \brief Emitted when the candlestick item brush is changed. + \sa brush +*/ + +/*! + \fn void QCandlestickSet::penChanged() + \brief Emitted when the candlestick item pen is changed. + \sa pen +*/ + +/*! + Constructs a QCandlestickSet with an optional \a timestamp and a \a parent. +*/ +QCandlestickSet::QCandlestickSet(qreal timestamp, QObject *parent) + : QObject(parent), + d_ptr(new QCandlestickSetPrivate(timestamp, this)) +{ +} + +/*! + Constructs a QCandlestickSet with given ordered values. The values \a open, \a high, \a low + and \a close are mandatory. The values \a timestamp and \a parent are optional. +*/ +QCandlestickSet::QCandlestickSet(qreal open, qreal high, qreal low, qreal close, qreal timestamp, + QObject *parent) + : QObject(parent), + d_ptr(new QCandlestickSetPrivate(timestamp, this)) +{ + Q_D(QCandlestickSet); + + d->m_open = open; + d->m_high = high; + d->m_low = low; + d->m_close = close; + + emit d->updatedLayout(); +} + +/*! + Destroys the set. +*/ +QCandlestickSet::~QCandlestickSet() +{ +} + +void QCandlestickSet::setTimestamp(qreal timestamp) +{ + Q_D(QCandlestickSet); + + bool changed = d->setTimestamp(timestamp); + if (!changed) + return; + + emit d->updatedLayout(); + emit timestampChanged(); +} + +qreal QCandlestickSet::timestamp() const +{ + Q_D(const QCandlestickSet); + + return d->m_timestamp; +} + +void QCandlestickSet::setOpen(qreal open) +{ + Q_D(QCandlestickSet); + + if (d->m_open == open) + return; + + d->m_open = open; + + emit d->updatedLayout(); + emit openChanged(); +} + +qreal QCandlestickSet::open() const +{ + Q_D(const QCandlestickSet); + + return d->m_open; +} + +void QCandlestickSet::setHigh(qreal high) +{ + Q_D(QCandlestickSet); + + if (d->m_high == high) + return; + + d->m_high = high; + + emit d->updatedLayout(); + emit highChanged(); +} + +qreal QCandlestickSet::high() const +{ + Q_D(const QCandlestickSet); + + return d->m_high; +} + +void QCandlestickSet::setLow(qreal low) +{ + Q_D(QCandlestickSet); + + if (d->m_low == low) + return; + + d->m_low = low; + + emit d->updatedLayout(); + emit lowChanged(); +} + +qreal QCandlestickSet::low() const +{ + Q_D(const QCandlestickSet); + + return d->m_low; +} + +void QCandlestickSet::setClose(qreal close) +{ + Q_D(QCandlestickSet); + + if (d->m_close == close) + return; + + d->m_close = close; + + emit d->updatedLayout(); + emit closeChanged(); +} + +qreal QCandlestickSet::close() const +{ + Q_D(const QCandlestickSet); + + return d->m_close; +} + +void QCandlestickSet::setBrush(const QBrush &brush) +{ + Q_D(QCandlestickSet); + + if (d->m_brush == brush) + return; + + d->m_brush = brush; + + emit d->updatedCandlestick(); + emit brushChanged(); +} + +QBrush QCandlestickSet::brush() const +{ + Q_D(const QCandlestickSet); + + return d->m_brush; +} + +void QCandlestickSet::setPen(const QPen &pen) +{ + Q_D(QCandlestickSet); + + if (d->m_pen == pen) + return; + + d->m_pen = pen; + + emit d->updatedCandlestick(); + emit penChanged(); +} + +QPen QCandlestickSet::pen() const +{ + Q_D(const QCandlestickSet); + + return d->m_pen; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +QCandlestickSetPrivate::QCandlestickSetPrivate(qreal timestamp, QCandlestickSet *parent) + : QObject(parent), + q_ptr(parent), + m_timestamp(0.0), + m_open(0.0), + m_high(0.0), + m_low(0.0), + m_close(0.0), + m_brush(QBrush(Qt::NoBrush)), + m_pen(QPen(Qt::NoPen)), + m_series(nullptr) +{ + setTimestamp(timestamp); +} + +QCandlestickSetPrivate::~QCandlestickSetPrivate() +{ +} + +bool QCandlestickSetPrivate::setTimestamp(qreal timestamp) +{ + timestamp = qMax(timestamp, 0.0); + timestamp = qRound64(timestamp); + + if (m_timestamp == timestamp) + return false; + + m_timestamp = timestamp; + + return true; +} + +#include "moc_qcandlestickset.cpp" +#include "moc_qcandlestickset_p.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/candlestickchart/qcandlestickset.h b/src/charts/candlestickchart/qcandlestickset.h new file mode 100644 index 0000000..ddbb6c1 --- /dev/null +++ b/src/charts/candlestickchart/qcandlestickset.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCANDLESTICKSET_H +#define QCANDLESTICKSET_H + +#include +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +class QCandlestickSetPrivate; + +class QT_CHARTS_EXPORT QCandlestickSet : public QObject +{ + Q_OBJECT + Q_PROPERTY(qreal timestamp READ timestamp WRITE setTimestamp NOTIFY timestampChanged) + Q_PROPERTY(qreal open READ open WRITE setOpen NOTIFY openChanged) + Q_PROPERTY(qreal high READ high WRITE setHigh NOTIFY highChanged) + Q_PROPERTY(qreal low READ low WRITE setLow NOTIFY lowChanged) + Q_PROPERTY(qreal close READ close WRITE setClose NOTIFY closeChanged) + Q_PROPERTY(QBrush brush READ brush WRITE setBrush NOTIFY brushChanged) + Q_PROPERTY(QPen pen READ pen WRITE setPen NOTIFY penChanged) + +public: + explicit QCandlestickSet(qreal timestamp = 0.0, QObject *parent = nullptr); + explicit QCandlestickSet(qreal open, qreal high, qreal low, qreal close, qreal timestamp = 0.0, + QObject *parent = nullptr); + virtual ~QCandlestickSet(); + + void setTimestamp(qreal timestamp); + qreal timestamp() const; + + void setOpen(qreal open); + qreal open() const; + + void setHigh(qreal high); + qreal high() const; + + void setLow(qreal low); + qreal low() const; + + void setClose(qreal close); + qreal close() const; + + void setBrush(const QBrush &brush); + QBrush brush() const; + + void setPen(const QPen &pen); + QPen pen() const; + +Q_SIGNALS: + void clicked(); + void hovered(bool status); + void pressed(); + void released(); + void doubleClicked(); + void timestampChanged(); + void openChanged(); + void highChanged(); + void lowChanged(); + void closeChanged(); + void brushChanged(); + void penChanged(); + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QCandlestickSet) + Q_DISABLE_COPY(QCandlestickSet) + friend class QCandlestickSeriesPrivate; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QCANDLESTICKSET_H diff --git a/src/charts/candlestickchart/qcandlestickset_p.h b/src/charts/candlestickchart/qcandlestickset_p.h new file mode 100644 index 0000000..327e4ee --- /dev/null +++ b/src/charts/candlestickchart/qcandlestickset_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef QCANDLESTICKSET_P_H +#define QCANDLESTICKSET_P_H + +#include +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +class QCandlestickSeriesPrivate; +class QCandlestickSet; + +class QCandlestickSetPrivate : public QObject +{ + Q_OBJECT + +public: + QCandlestickSetPrivate(qreal timestamp, QCandlestickSet *parent); + ~QCandlestickSetPrivate(); + + bool setTimestamp(qreal timestamp); + +Q_SIGNALS: + void updatedLayout(); + void updatedCandlestick(); + +private: + QCandlestickSet *q_ptr; + qreal m_timestamp; + qreal m_open; + qreal m_high; + qreal m_low; + qreal m_close; + QBrush m_brush; + QPen m_pen; + QCandlestickSeriesPrivate *m_series; + +private: + Q_DECLARE_PUBLIC(QCandlestickSet) + friend class QCandlestickSeriesPrivate; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QCANDLESTICKSET_P_H diff --git a/src/charts/candlestickchart/qhcandlestickmodelmapper.cpp b/src/charts/candlestickchart/qhcandlestickmodelmapper.cpp new file mode 100644 index 0000000..b27d37d --- /dev/null +++ b/src/charts/candlestickchart/qhcandlestickmodelmapper.cpp @@ -0,0 +1,322 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +/*! + \class QHCandlestickModelMapper + \since 5.8 + \inmodule Qt Charts + \brief Horizontal model mapper for a candlestick series. + + Model mappers allow the use of a QAbstractItemModel-derived model as a data source for a chart + series, creating a connection between a QCandlestickSeries and the model object. A horizontal + model mapper maintains an equal size across all \l {QCandlestickSet} {QCandlestickSets}, and + reads the values of the set from the model's rows. + + \note The model used must support adding and removing rows/columns and modifying the data of the + cells. +*/ + +/*! + \qmltype HCandlestickModelMapper + \since 2.2 + \instantiates QHCandlestickModelMapper + \inqmlmodule QtCharts + \brief Horizontal model mapper for a candlestick series. + + HCandlestickModelMapper allows the use of a QAbstractItemModel-derived model with data in rows + as a data source for a candlestick series. It's possible to manipulate the data either through + QAbstractItemModel or QCandlestickSeries. + + The following QML example creates a candlestick series with three candlestick sets (assuming the + model has at least four rows). Each candlestick set would contain data defined by timestamp, + open, high, low and close columns. The name of a set would be defined by the vertical header of + the row. + \qml + CandlestickSeries { + HCandlestickModelMapper { + model: myCustomModel // QAbstractItemModel derived implementation + timestampColumn: 1 + openColumn: 2 + highColumn: 3 + lowColumn: 4 + closeColumn: 5 + firstCandlestickSetRow: 1 + lastCandlestickSetRow: 3 + } + } + \endqml + + \note HCandlestickModelMapper keeps the series and the model in sync. +*/ + +/*! + \qmlproperty QAbstractItemModel HCandlestickModelMapper::model + \brief The QAbstractItemModel-based model that is used by the mapper. The model must be + implemented and exposed to QML. + + \note The model used must support adding and removing rows/columns and modifying the data of the + cells. +*/ + +/*! + \qmlproperty CandlestickSeries HCandlestickModelMapper::series + \brief Defines the CandlestickSeries based object that is used by the mapper. + + All the data in the series is discarded when it is set to the mapper. When a new series is + specified, the old series is disconnected (preserving its data). +*/ + +/*! + \property QHCandlestickModelMapper::timestampColumn + \brief Defines the column of the model that contains the timestamp values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \qmlproperty int HCandlestickModelMapper::timestampColumn + \brief Defines the column of the model that contains the timestamp values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \property QHCandlestickModelMapper::openColumn + \brief Defines the column of the model that contains the open values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \qmlproperty int HCandlestickModelMapper::openColumn + \brief Defines the column of the model that contains the open values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \property QHCandlestickModelMapper::highColumn + \brief Defines the column of the model that contains the high values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \qmlproperty int HCandlestickModelMapper::highColumn + \brief Defines the column of the model that contains the high values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \property QHCandlestickModelMapper::lowColumn + \brief Defines the column of the model that contains the low values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \qmlproperty int HCandlestickModelMapper::lowColumn + \brief Defines the column of the model that contains the low values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \property QHCandlestickModelMapper::closeColumn + \brief Defines the column of the model that contains the close values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \qmlproperty int HCandlestickModelMapper::closeColumn + \brief Defines the column of the model that contains the close values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \property QHCandlestickModelMapper::firstCandlestickSetRow + \brief Defines the row of the model that is used as the data source for the first set. Default + value is -1 (invalid mapping). +*/ + +/*! + \qmlproperty int HCandlestickModelMapper::firstCandlestickSetRow + \brief Defines the row of the model that is used as the data source for the first set. Default + value is -1 (invalid mapping). +*/ + +/*! + \property QHCandlestickModelMapper::lastCandlestickSetRow + \brief Defines the row of the model that is used as the data source for the last set. Default + value is -1 (invalid mapping). +*/ + +/*! + \qmlproperty int HCandlestickModelMapper::lastCandlestickSetRow + \brief Defines the row of the model that is used as the data source for the last set. Default + value is -1 (invalid mapping). +*/ + +/*! + \fn void QHCandlestickModelMapper::timestampColumnChanged() + \brief Emitted when the column of the model that contains timestamp values is changed + \sa timestampColumn +*/ + +/*! + \fn void QHCandlestickModelMapper::openColumnChanged() + \brief Emitted when the column of the model that contains open values is changed. + \sa openColumn +*/ +/*! + \fn void QHCandlestickModelMapper::highColumnChanged() + \brief Emitted when the column of the model that contains high values is changed. + \sa highColumn +*/ + +/*! + \fn void QHCandlestickModelMapper::lowColumnChanged() + \brief Emitted when the column of the model that contains low values is changed. + \sa lowColumn +*/ + +/*! + \fn void QHCandlestickModelMapper::closeColumnChanged() + \brief Emitted when the column of the model that contains close values is changed. + \sa closeColumn +*/ + +/*! + \fn void QHCandlestickModelMapper::firstCandlestickSetRowChanged() + \brief Emitted when the row of the model that contains the data of the first set is changed. + \sa firstCandlestickSetRow +*/ + +/*! + \fn void QHCandlestickModelMapper::lastCandlestickSetRowChanged() + \brief Emitted when the row of the model that contains the data of the last set is changed. + \sa lastCandlestickSetRow +*/ + +/*! + Constructs a horizontal model mapper object which is a child of \a parent. +*/ +QHCandlestickModelMapper::QHCandlestickModelMapper(QObject *parent) + : QCandlestickModelMapper(parent) +{ + connect(d_ptr, SIGNAL(timestampChanged()), this, SIGNAL(timestampColumnChanged())); + connect(d_ptr, SIGNAL(openChanged()), this, SIGNAL(openColumnChanged())); + connect(d_ptr, SIGNAL(highChanged()), this, SIGNAL(highColumnChanged())); + connect(d_ptr, SIGNAL(lowChanged()), this, SIGNAL(lowColumnChanged())); + connect(d_ptr, SIGNAL(closeChanged()), this, SIGNAL(closeColumnChanged())); + connect(d_ptr, SIGNAL(firstCandlestickSetSectionChanged()), + this, SIGNAL(firstCandlestickSetRowChanged())); + connect(d_ptr, SIGNAL(lastCandlestickSetSectionChanged()), + this, SIGNAL(lastCandlestickSetRowChanged())); +} + +/*! + Returns Qt::Horizontal. This means that values of the set are read from rows. +*/ +Qt::Orientation QHCandlestickModelMapper::orientation() const +{ + return Qt::Horizontal; +} + +void QHCandlestickModelMapper::setTimestampColumn(int timestampColumn) +{ + QCandlestickModelMapper::setTimestamp(timestampColumn); +} + +int QHCandlestickModelMapper::timestampColumn() const +{ + return QCandlestickModelMapper::timestamp(); +} + +void QHCandlestickModelMapper::setOpenColumn(int openColumn) +{ + QCandlestickModelMapper::setOpen(openColumn); +} + +int QHCandlestickModelMapper::openColumn() const +{ + return QCandlestickModelMapper::open(); +} + +void QHCandlestickModelMapper::setHighColumn(int highColumn) +{ + QCandlestickModelMapper::setHigh(highColumn); +} + +int QHCandlestickModelMapper::highColumn() const +{ + return QCandlestickModelMapper::high(); +} + +void QHCandlestickModelMapper::setLowColumn(int lowColumn) +{ + QCandlestickModelMapper::setLow(lowColumn); +} + +int QHCandlestickModelMapper::lowColumn() const +{ + return QCandlestickModelMapper::low(); +} + +void QHCandlestickModelMapper::setCloseColumn(int closeColumn) +{ + QCandlestickModelMapper::setClose(closeColumn); +} + +int QHCandlestickModelMapper::closeColumn() const +{ + return QCandlestickModelMapper::close(); +} + +void QHCandlestickModelMapper::setFirstCandlestickSetRow(int firstCandlestickSetRow) +{ + QCandlestickModelMapper::setFirstCandlestickSetSection(firstCandlestickSetRow); +} + +int QHCandlestickModelMapper::firstCandlestickSetRow() const +{ + return QCandlestickModelMapper::firstCandlestickSetSection(); +} + +void QHCandlestickModelMapper::setLastCandlestickSetRow(int lastCandlestickSetRow) +{ + QCandlestickModelMapper::setLastCandlestickSetSection(lastCandlestickSetRow); +} + +int QHCandlestickModelMapper::lastCandlestickSetRow() const +{ + return QCandlestickModelMapper::lastCandlestickSetSection(); +} + +#include "moc_qhcandlestickmodelmapper.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/candlestickchart/qhcandlestickmodelmapper.h b/src/charts/candlestickchart/qhcandlestickmodelmapper.h new file mode 100644 index 0000000..5bae93c --- /dev/null +++ b/src/charts/candlestickchart/qhcandlestickmodelmapper.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHCANDLESTICKMODELMAPPER_H +#define QHCANDLESTICKMODELMAPPER_H + +#include + +QT_CHARTS_BEGIN_NAMESPACE +/* Comment line for syncqt to generate the fwd-include correctly, due to QTBUG-22432 */ +class QT_CHARTS_EXPORT QHCandlestickModelMapper : public QCandlestickModelMapper +{ + Q_OBJECT + Q_PROPERTY(int timestampColumn READ timestampColumn WRITE setTimestampColumn NOTIFY timestampColumnChanged) + Q_PROPERTY(int openColumn READ openColumn WRITE setOpenColumn NOTIFY openColumnChanged) + Q_PROPERTY(int highColumn READ highColumn WRITE setHighColumn NOTIFY highColumnChanged) + Q_PROPERTY(int lowColumn READ lowColumn WRITE setLowColumn NOTIFY lowColumnChanged) + Q_PROPERTY(int closeColumn READ closeColumn WRITE setCloseColumn NOTIFY closeColumnChanged) + Q_PROPERTY(int firstCandlestickSetRow READ firstCandlestickSetRow WRITE setFirstCandlestickSetRow NOTIFY firstCandlestickSetRowChanged) + Q_PROPERTY(int lastCandlestickSetRow READ lastCandlestickSetRow WRITE setLastCandlestickSetRow NOTIFY lastCandlestickSetRowChanged) + +public: + explicit QHCandlestickModelMapper(QObject *parent = nullptr); + + Qt::Orientation orientation() const; + + void setTimestampColumn(int timestampColumn); + int timestampColumn() const; + + void setOpenColumn(int openColumn); + int openColumn() const; + + void setHighColumn(int highColumn); + int highColumn() const; + + void setLowColumn(int lowColumn); + int lowColumn() const; + + void setCloseColumn(int closeColumn); + int closeColumn() const; + + void setFirstCandlestickSetRow(int firstCandlestickSetRow); + int firstCandlestickSetRow() const; + + void setLastCandlestickSetRow(int lastCandlestickSetRow); + int lastCandlestickSetRow() const; + +Q_SIGNALS: + void timestampColumnChanged(); + void openColumnChanged(); + void highColumnChanged(); + void lowColumnChanged(); + void closeColumnChanged(); + void firstCandlestickSetRowChanged(); + void lastCandlestickSetRowChanged(); +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QHCANDLESTICKMODELMAPPER_H diff --git a/src/charts/candlestickchart/qvcandlestickmodelmapper.cpp b/src/charts/candlestickchart/qvcandlestickmodelmapper.cpp new file mode 100644 index 0000000..990c438 --- /dev/null +++ b/src/charts/candlestickchart/qvcandlestickmodelmapper.cpp @@ -0,0 +1,323 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +/*! + \class QVCandlestickModelMapper + \since 5.8 + \inmodule Qt Charts + \brief Vertical model mapper for a candlestick series. + + Model mappers allow the use of a QAbstractItemModel-derived model as a data source for a chart + series, creating a connection between a QCandlestickSeries and the model object. A vertical + model mapper maintains an equal size across all \l {QCandlestickSet} {QCandlestickSets}, and + reads the values of the set from the model's columns. + + \note The model used must support adding and removing rows/columns and modifying the data of the + cells. +*/ + +/*! + \qmltype VCandlestickModelMapper + \since 2.2 + \instantiates QVCandlestickModelMapper + \inqmlmodule QtCharts + \brief Vertical model mapper for a candlestick series. + + VCandlestickModelMapper allows the use of a QAbstractItemModel-derived model with data in + columns as a data source for a candlestick series. It's possible to manipulate the data either + through QAbstractItemModel or QCandlestickSeries. + + The following QML example creates a candlestick series with three candlestick sets (assuming the + model has at least four columns). Each candlestick set would contain data defined by timestamp, + open, high, low and close rows. The name of a set would be defined by the horizontal header of + the column. + \qml + CandlestickSeries { + VCandlestickModelMapper { + model: myCustomModel // QAbstractItemModel derived implementation + timestampRow: 1 + openRow: 2 + highRow: 3 + lowRow: 4 + closeRow: 5 + firstCandlestickSetColumn: 1 + lastCandlestickSetColumn: 3 + } + } + \endqml + + \note VCandlestickModelMapper keeps the series and the model in sync. +*/ + +/*! + \qmlproperty QAbstractItemModel VCandlestickModelMapper::model + \brief The QAbstractItemModel-based model that is used by the mapper. The model must be + implemented and exposed to QML. + + \note The model used must support adding and removing rows/columns and modifying the data of the + cells. +*/ + +/*! + \qmlproperty CandlestickSeries VCandlestickModelMapper::series + \brief Defines the CandlestickSeries based object that is used by the mapper. + + All the data in the series is discarded when it is set to the mapper. When a new series is + specified, the old series is disconnected (preserving its data). +*/ + +/*! + \property QVCandlestickModelMapper::timestampRow + \brief Defines the row of the model that contains the timestamp values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \qmlproperty int VCandlestickModelMapper::timestampRow + \brief Defines the row of the model that contains the timestamp values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \property QVCandlestickModelMapper::openRow + \brief Defines the row of the model that contains the open values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \qmlproperty int VCandlestickModelMapper::openRow + \brief Defines the row of the model that contains the open values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \property QVCandlestickModelMapper::highRow + \brief Defines the row of the model that contains the high values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \qmlproperty int VCandlestickModelMapper::highRow + \brief Defines the row of the model that contains the high values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \property QVCandlestickModelMapper::lowRow + \brief Defines the row of the model that contains the low values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \qmlproperty int VCandlestickModelMapper::lowRow + \brief Defines the row of the model that contains the low values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \property QVCandlestickModelMapper::closeRow + \brief Defines the row of the model that contains the close values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \qmlproperty int VCandlestickModelMapper::closeRow + \brief Defines the row of the model that contains the close values of the + \l {QCandlestickSet} {QCandlestickSets} in the series. Default value is -1 (invalid mapping). +*/ + +/*! + \property QVCandlestickModelMapper::firstCandlestickSetColumn + \brief Defines the column of the model that is used as the data source for the first set. + Default value is -1 (invalid mapping). +*/ + +/*! + \qmlproperty int VCandlestickModelMapper::firstCandlestickSetColumn + \brief Defines the column of the model that is used as the data source for the first set. + Default value is -1 (invalid mapping). +*/ + +/*! + \property QVCandlestickModelMapper::lastCandlestickSetColumn + \brief Defines the column of the model that is used as the data source for the last set. Default + value is -1 (invalid mapping). +*/ + +/*! + \qmlproperty int VCandlestickModelMapper::lastCandlestickSetColumn + \brief Defines the column of the model that is used as the data source for the last set. Default + value is -1 (invalid mapping). +*/ + +/*! + \fn void QVCandlestickModelMapper::timestampRowChanged() + \brief Emitted when the row of the model that contains timestamp values is changed. + \sa timestampRow +*/ + +/*! + \fn void QVCandlestickModelMapper::openRowChanged() + \brief Emitted when the row of the model that contains open values is changed. + \sa openRow +*/ + +/*! + \fn void QVCandlestickModelMapper::highRowChanged() + \brief Emitted when the row of the model that contains high values is changed. + \sa highRow +*/ + +/*! + \fn void QVCandlestickModelMapper::lowRowChanged() + \brief Emitted when the row of the model that contains low values is changed. + \sa lowRow +*/ + +/*! + \fn void QVCandlestickModelMapper::closeRowChanged() + \brief Emitted when the row of the model that contains close values is changed. + \sa closeRow +*/ + +/*! + \fn void QVCandlestickModelMapper::firstCandlestickSetColumnChanged() + \brief Emitted when the column of the model that contains the data of the first set is changed. + \sa firstCandlestickSetColumn +*/ + +/*! + \fn void QVCandlestickModelMapper::lastCandlestickSetColumnChanged() + \brief Emitted when the column of the model that contains the data of the last set is changed. + \sa lastCandlestickSetColumn +*/ + +/*! + Constructs a vertical model mapper object which is a child of \a parent. +*/ +QVCandlestickModelMapper::QVCandlestickModelMapper(QObject *parent) + : QCandlestickModelMapper(parent) +{ + connect(d_ptr, SIGNAL(timestampChanged()), this, SIGNAL(timestampRowChanged())); + connect(d_ptr, SIGNAL(openChanged()), this, SIGNAL(openRowChanged())); + connect(d_ptr, SIGNAL(highChanged()), this, SIGNAL(highRowChanged())); + connect(d_ptr, SIGNAL(lowChanged()), this, SIGNAL(lowRowChanged())); + connect(d_ptr, SIGNAL(closeChanged()), this, SIGNAL(closeRowChanged())); + connect(d_ptr, SIGNAL(firstCandlestickSetSectionChanged()), + this, SIGNAL(firstCandlestickSetColumnChanged())); + connect(d_ptr, SIGNAL(lastCandlestickSetSectionChanged()), + this, SIGNAL(lastCandlestickSetColumnChanged())); +} + +/*! + Returns Qt::Vertical. This means that values of the set are read from columns. +*/ +Qt::Orientation QVCandlestickModelMapper::orientation() const +{ + return Qt::Vertical; +} + +void QVCandlestickModelMapper::setTimestampRow(int timestampRow) +{ + QCandlestickModelMapper::setTimestamp(timestampRow); +} + +int QVCandlestickModelMapper::timestampRow() const +{ + return QCandlestickModelMapper::timestamp(); +} + +void QVCandlestickModelMapper::setOpenRow(int openRow) +{ + QCandlestickModelMapper::setOpen(openRow); +} + +int QVCandlestickModelMapper::openRow() const +{ + return QCandlestickModelMapper::open(); +} + +void QVCandlestickModelMapper::setHighRow(int highRow) +{ + QCandlestickModelMapper::setHigh(highRow); +} + +int QVCandlestickModelMapper::highRow() const +{ + return QCandlestickModelMapper::high(); +} + +void QVCandlestickModelMapper::setLowRow(int lowRow) +{ + QCandlestickModelMapper::setLow(lowRow); +} + +int QVCandlestickModelMapper::lowRow() const +{ + return QCandlestickModelMapper::low(); +} + +void QVCandlestickModelMapper::setCloseRow(int closeRow) +{ + QCandlestickModelMapper::setClose(closeRow); +} + +int QVCandlestickModelMapper::closeRow() const +{ + return QCandlestickModelMapper::close(); +} + +void QVCandlestickModelMapper::setFirstCandlestickSetColumn(int firstCandlestickSetColumn) +{ + QCandlestickModelMapper::setFirstCandlestickSetSection(firstCandlestickSetColumn); +} + +int QVCandlestickModelMapper::firstCandlestickSetColumn() const +{ + return QCandlestickModelMapper::firstCandlestickSetSection(); +} + +void QVCandlestickModelMapper::setLastCandlestickSetColumn(int lastCandlestickSetColumn) +{ + QCandlestickModelMapper::setLastCandlestickSetSection(lastCandlestickSetColumn); +} + +int QVCandlestickModelMapper::lastCandlestickSetColumn() const +{ + return QCandlestickModelMapper::lastCandlestickSetSection(); +} + +#include "moc_qvcandlestickmodelmapper.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/candlestickchart/qvcandlestickmodelmapper.h b/src/charts/candlestickchart/qvcandlestickmodelmapper.h new file mode 100644 index 0000000..5cd20f9 --- /dev/null +++ b/src/charts/candlestickchart/qvcandlestickmodelmapper.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QVCANDLESTICKMODELMAPPER_H +#define QVCANDLESTICKMODELMAPPER_H + +#include + +QT_CHARTS_BEGIN_NAMESPACE +/* Comment line for syncqt to generate the fwd-include correctly, due to QTBUG-22432 */ +class QT_CHARTS_EXPORT QVCandlestickModelMapper : public QCandlestickModelMapper +{ + Q_OBJECT + Q_PROPERTY(int timestampRow READ timestampRow WRITE setTimestampRow NOTIFY timestampRowChanged) + Q_PROPERTY(int openRow READ openRow WRITE setOpenRow NOTIFY openRowChanged) + Q_PROPERTY(int highRow READ highRow WRITE setHighRow NOTIFY highRowChanged) + Q_PROPERTY(int lowRow READ lowRow WRITE setLowRow NOTIFY lowRowChanged) + Q_PROPERTY(int closeRow READ closeRow WRITE setCloseRow NOTIFY closeRowChanged) + Q_PROPERTY(int firstCandlestickSetColumn READ firstCandlestickSetColumn WRITE setFirstCandlestickSetColumn NOTIFY firstCandlestickSetColumnChanged) + Q_PROPERTY(int lastCandlestickSetColumn READ lastCandlestickSetColumn WRITE setLastCandlestickSetColumn NOTIFY lastCandlestickSetColumnChanged) + +public: + explicit QVCandlestickModelMapper(QObject *parent = nullptr); + + Qt::Orientation orientation() const; + + void setTimestampRow(int timestampRow); + int timestampRow() const; + + void setOpenRow(int openRow); + int openRow() const; + + void setHighRow(int highRow); + int highRow() const; + + void setLowRow(int lowRow); + int lowRow() const; + + void setCloseRow(int closeRow); + int closeRow() const; + + void setFirstCandlestickSetColumn(int firstCandlestickSetColumn); + int firstCandlestickSetColumn() const; + + void setLastCandlestickSetColumn(int lastCandlestickSetColumn); + int lastCandlestickSetColumn() const; + +Q_SIGNALS: + void timestampRowChanged(); + void openRowChanged(); + void highRowChanged(); + void lowRowChanged(); + void closeRowChanged(); + void firstCandlestickSetColumnChanged(); + void lastCandlestickSetColumnChanged(); +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QVCANDLESTICKMODELMAPPER_H diff --git a/src/charts/chartdataset.cpp b/src/charts/chartdataset.cpp index cbc4dfb..97602b5 100644 --- a/src/charts/chartdataset.cpp +++ b/src/charts/chartdataset.cpp @@ -153,20 +153,19 @@ void ChartDataSet::addAxis(QAbstractAxis *axis, Qt::Alignment aligment) */ void ChartDataSet::removeSeries(QAbstractSeries *series) { - if (! m_seriesList.contains(series)) { qWarning() << QObject::tr("Can not remove series. Series not found on the chart."); return; } - QList axes = series->d_ptr->m_axes; + QList axes = series->d_ptr->m_axes; - foreach(QAbstractAxis* axis, axes) { - detachAxis(series,axis); + foreach (QAbstractAxis *axis, axes) { + detachAxis(series, axis); } - emit seriesRemoved(series); m_seriesList.removeAll(series); + emit seriesRemoved(series); // Reset domain to default series->d_ptr->setDomain(new XYDomain()); diff --git a/src/charts/chartpresenter_p.h b/src/charts/chartpresenter_p.h index 5b71379..e3db5ff 100644 --- a/src/charts/chartpresenter_p.h +++ b/src/charts/chartpresenter_p.h @@ -79,6 +79,7 @@ public: ScatterSeriesZValue = SeriesZValue, PieSeriesZValue = SeriesZValue, BoxPlotSeriesZValue = SeriesZValue, + CandlestickSeriesZValue = SeriesZValue, LegendZValue, TopMostZValue }; diff --git a/src/charts/charts.pro b/src/charts/charts.pro index 30546fd..20f9d00 100644 --- a/src/charts/charts.pro +++ b/src/charts/charts.pro @@ -77,6 +77,7 @@ include($$PWD/themes/themes.pri) include($$PWD/xychart/xychart.pri) include($$PWD/layout/layout.pri) include($$PWD/boxplotchart/boxplotchart.pri) +include($$PWD/candlestickchart/candlestickchart.pri) HEADERS += $$PUBLIC_HEADERS HEADERS += $$PRIVATE_HEADERS diff --git a/src/charts/common.pri b/src/charts/common.pri index 1d5b649..18dee55 100644 --- a/src/charts/common.pri +++ b/src/charts/common.pri @@ -4,6 +4,7 @@ INCLUDEPATH += $$PWD/ \ $$PWD/axis \ $$PWD/barchart \ $$PWD/boxplotchart \ + $$PWD/candlestickchart \ $$PWD/domain \ $$PWD/layout \ $$PWD/legend \ diff --git a/src/charts/doc/images/examples_candlestickchart.png b/src/charts/doc/images/examples_candlestickchart.png new file mode 100644 index 0000000000000000000000000000000000000000..ac8cebeb9333df29c4180e4875b56d3765aeb88e GIT binary patch literal 21063 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

2D@a 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 literal 0 Hc$@setFont(font); + QFontMetrics fn(font); - m_markerRect = QRectF(0,0,fn.height()/2,fn.height()/2); + QRectF markerRect = QRectF(0, 0, fn.height() / 2, fn.height() / 2); + if (m_markerRect != markerRect) { + m_markerRect = markerRect; + emit markerRectChanged(); + } + updateGeometry(); } @@ -152,6 +158,11 @@ QRectF LegendMarkerItem::boundingRect() const return m_boundingRect; } +QRectF LegendMarkerItem::markerRect() const +{ + return m_markerRect; +} + void LegendMarkerItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option) diff --git a/src/charts/legend/legendmarkeritem_p.h b/src/charts/legend/legendmarkeritem_p.h index 870cfb2..c337174 100644 --- a/src/charts/legend/legendmarkeritem_p.h +++ b/src/charts/legend/legendmarkeritem_p.h @@ -56,7 +56,7 @@ class LegendMarkerItem : public QGraphicsObject, public QGraphicsLayoutItem Q_OBJECT Q_INTERFACES(QGraphicsLayoutItem) public: - explicit LegendMarkerItem(QLegendMarkerPrivate *marker, QGraphicsObject *parent = 0); + explicit LegendMarkerItem(QLegendMarkerPrivate *marker, QGraphicsObject *parent = nullptr); ~LegendMarkerItem(); void setPen(const QPen &pen); @@ -76,8 +76,9 @@ public: void setGeometry(const QRectF &rect); QRectF boundingRect() const; + QRectF markerRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget = nullptr); QSizeF sizeHint (Qt::SizeHint which, const QSizeF &constraint) const; void hoverEnterEvent(QGraphicsSceneHoverEvent *event); @@ -85,6 +86,10 @@ public: QString displayedLabel() const; void setToolTip(const QString &tooltip); + +Q_SIGNALS: + void markerRectChanged(); + protected: QLegendMarkerPrivate *m_marker; // Knows QRectF m_markerRect; diff --git a/src/charts/legend/qcandlesticklegendmarker.cpp b/src/charts/legend/qcandlesticklegendmarker.cpp new file mode 100644 index 0000000..97208e3 --- /dev/null +++ b/src/charts/legend/qcandlesticklegendmarker.cpp @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +QCandlestickLegendMarker::QCandlestickLegendMarker(QCandlestickSeries *series, QLegend *legend, + QObject *parent) + : QLegendMarker(*new QCandlestickLegendMarkerPrivate(this, series, legend), parent) +{ + Q_D(QCandlestickLegendMarker); + + d->updated(); +} + +QCandlestickLegendMarker::~QCandlestickLegendMarker() +{ +} + +QLegendMarker::LegendMarkerType QCandlestickLegendMarker::type() +{ + return LegendMarkerTypeCandlestick; +} + +QCandlestickSeries* QCandlestickLegendMarker::series() +{ + Q_D(QCandlestickLegendMarker); + + return d->m_series; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +QCandlestickLegendMarkerPrivate::QCandlestickLegendMarkerPrivate(QCandlestickLegendMarker *q, + QCandlestickSeries *series, + QLegend *legend) + : QLegendMarkerPrivate(q, legend), + q_ptr(q), + m_series(series) +{ + QObject::connect(m_item, SIGNAL(markerRectChanged()), this, SLOT(updated())); + QObject::connect(m_series, SIGNAL(nameChanged()), this, SLOT(updated())); + QObject::connect(m_series->d_func(), SIGNAL(updated()), this, SLOT(updated())); +} + +QCandlestickLegendMarkerPrivate::~QCandlestickLegendMarkerPrivate() +{ +} + +QAbstractSeries* QCandlestickLegendMarkerPrivate::series() +{ + return m_series; +} + +QObject* QCandlestickLegendMarkerPrivate::relatedObject() +{ + return m_series; +} + +void QCandlestickLegendMarkerPrivate::updated() +{ + bool labelChanged = false; + bool brushChanged = false; + + if (!m_customLabel && (m_item->label() != m_series->name())) { + m_item->setLabel(m_series->name()); + labelChanged = true; + } + if (!m_customBrush) { + QLinearGradient gradient; + gradient.setStart(0.0, 0.0); + gradient.setFinalStop(m_item->markerRect().width(), m_item->markerRect().height()); + gradient.setColorAt(0.0, m_series->increasingColor()); + gradient.setColorAt(0.49, m_series->increasingColor()); + gradient.setColorAt(0.50, m_series->decreasingColor()); + gradient.setColorAt(1.0, m_series->decreasingColor()); + + QBrush brush(gradient); + if (m_item->brush() != brush) { + m_item->setBrush(brush); + brushChanged = true; + } + } + invalidateLegend(); + + if (labelChanged) + emit q_ptr->labelChanged(); + if (brushChanged) + emit q_ptr->brushChanged(); +} + +#include "moc_qcandlesticklegendmarker.cpp" +#include "moc_qcandlesticklegendmarker_p.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/legend/qcandlesticklegendmarker.h b/src/charts/legend/qcandlesticklegendmarker.h new file mode 100644 index 0000000..dad57c4 --- /dev/null +++ b/src/charts/legend/qcandlesticklegendmarker.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCANDLESTICKLEGENDMARKER_H +#define QCANDLESTICKLEGENDMARKER_H + +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +class QCandlestickLegendMarkerPrivate; + +class QT_CHARTS_EXPORT QCandlestickLegendMarker : public QLegendMarker +{ + Q_OBJECT + +public: + explicit QCandlestickLegendMarker(QCandlestickSeries *series, QLegend *legend, + QObject *parent = nullptr); + virtual ~QCandlestickLegendMarker(); + + virtual LegendMarkerType type(); + + // Related series + virtual QCandlestickSeries* series(); + +private: + Q_DECLARE_PRIVATE(QCandlestickLegendMarker) + Q_DISABLE_COPY(QCandlestickLegendMarker) +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QCANDLESTICKLEGENDMARKER_H diff --git a/src/charts/legend/qcandlesticklegendmarker_p.h b/src/charts/legend/qcandlesticklegendmarker_p.h new file mode 100644 index 0000000..1c786b3 --- /dev/null +++ b/src/charts/legend/qcandlesticklegendmarker_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef QCANDLESTICKLEGENDMARKER_P_H +#define QCANDLESTICKLEGENDMARKER_P_H + +#include + +QT_CHARTS_BEGIN_NAMESPACE + +class QCandlestickLegendMarker; +class QCandlestickSeries; + +class QCandlestickLegendMarkerPrivate : public QLegendMarkerPrivate +{ + Q_OBJECT +public: + explicit QCandlestickLegendMarkerPrivate(QCandlestickLegendMarker *q, + QCandlestickSeries *series, QLegend *legend); + virtual ~QCandlestickLegendMarkerPrivate(); + + virtual QAbstractSeries *series(); + virtual QObject *relatedObject(); + +public Q_SLOTS: + virtual void updated(); + +private: + QCandlestickLegendMarker *q_ptr; + QCandlestickSeries *m_series; + + Q_DECLARE_PUBLIC(QCandlestickLegendMarker) +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QCANDLESTICKLEGENDMARKER_P_H diff --git a/src/charts/legend/qlegendmarker.cpp b/src/charts/legend/qlegendmarker.cpp index 8d24087..b9da485 100644 --- a/src/charts/legend/qlegendmarker.cpp +++ b/src/charts/legend/qlegendmarker.cpp @@ -63,6 +63,7 @@ QT_CHARTS_BEGIN_NAMESPACE \value LegendMarkerTypePie \value LegendMarkerTypeXY \value LegendMarkerTypeBoxPlot + \value LegendMarkerTypeCandlestick */ /*! diff --git a/src/charts/legend/qlegendmarker.h b/src/charts/legend/qlegendmarker.h index 142b7e4..8672144 100644 --- a/src/charts/legend/qlegendmarker.h +++ b/src/charts/legend/qlegendmarker.h @@ -52,7 +52,8 @@ public: LegendMarkerTypeBar, LegendMarkerTypePie, LegendMarkerTypeXY, - LegendMarkerTypeBoxPlot + LegendMarkerTypeBoxPlot, + LegendMarkerTypeCandlestick }; Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged) diff --git a/src/charts/qabstractseries.cpp b/src/charts/qabstractseries.cpp index 31a7504..b5c7d8a 100644 --- a/src/charts/qabstractseries.cpp +++ b/src/charts/qabstractseries.cpp @@ -77,6 +77,7 @@ QT_CHARTS_BEGIN_NAMESPACE \value SeriesTypeHorizontalStackedBar \value SeriesTypeHorizontalPercentBar \value SeriesTypeBoxPlot + \value SeriesTypeCandlestick */ /*! diff --git a/src/charts/qabstractseries.h b/src/charts/qabstractseries.h index 8fedf5c..2d987ee 100644 --- a/src/charts/qabstractseries.h +++ b/src/charts/qabstractseries.h @@ -63,11 +63,12 @@ public: SeriesTypeHorizontalBar, SeriesTypeHorizontalStackedBar, SeriesTypeHorizontalPercentBar, - SeriesTypeBoxPlot + SeriesTypeBoxPlot, + SeriesTypeCandlestick }; protected: - QAbstractSeries(QAbstractSeriesPrivate &d, QObject *parent = Q_NULLPTR); + QAbstractSeries(QAbstractSeriesPrivate &d, QObject *parent = nullptr); public: ~QAbstractSeries(); diff --git a/src/charts/qchart.h b/src/charts/qchart.h index 40b53a8..cf2efb4 100644 --- a/src/charts/qchart.h +++ b/src/charts/qchart.h @@ -191,6 +191,7 @@ protected: friend class ChartThemeManager; friend class QAbstractSeries; friend class QBoxPlotSeriesPrivate; + friend class QCandlestickSeriesPrivate; private: Q_DISABLE_COPY(QChart) diff --git a/src/chartsqml2/chartsqml2.pro b/src/chartsqml2/chartsqml2.pro index fae7dc2..765f25b 100644 --- a/src/chartsqml2/chartsqml2.pro +++ b/src/chartsqml2/chartsqml2.pro @@ -36,7 +36,8 @@ SOURCES += \ declarativepolarchart.cpp \ declarativeboxplotseries.cpp \ declarativechartnode.cpp \ - declarativerendernode.cpp + declarativerendernode.cpp \ + declarativecandlestickseries.cpp HEADERS += \ declarativechart.h \ @@ -54,7 +55,8 @@ HEADERS += \ declarativepolarchart.h \ declarativeboxplotseries.h \ declarativechartnode.h \ - declarativerendernode.h + declarativerendernode.h \ + declarativecandlestickseries.h OTHER_FILES = qmldir diff --git a/src/chartsqml2/chartsqml2_plugin.cpp b/src/chartsqml2/chartsqml2_plugin.cpp index 5492a45..af1f15a 100644 --- a/src/chartsqml2/chartsqml2_plugin.cpp +++ b/src/chartsqml2/chartsqml2_plugin.cpp @@ -42,6 +42,7 @@ #include "declarativescatterseries.h" #include "declarativebarseries.h" #include "declarativeboxplotseries.h" +#include "declarativecandlestickseries.h" #include "declarativepieseries.h" #include "declarativeaxes.h" #include @@ -58,6 +59,9 @@ #include #include #include +#include +#include +#include #ifndef QT_QREAL_IS_FLOAT #include #endif @@ -78,6 +82,8 @@ QML_DECLARE_TYPE(DeclarativeBarSeries) QML_DECLARE_TYPE(DeclarativeBarSet) QML_DECLARE_TYPE(DeclarativeBoxPlotSeries) QML_DECLARE_TYPE(DeclarativeBoxSet) +QML_DECLARE_TYPE(DeclarativeCandlestickSeries) +QML_DECLARE_TYPE(DeclarativeCandlestickSet) QML_DECLARE_TYPE(DeclarativeLineSeries) QML_DECLARE_TYPE(DeclarativePieSeries) QML_DECLARE_TYPE(DeclarativePieSlice) @@ -112,6 +118,9 @@ QML_DECLARE_TYPE(QXYModelMapper) QML_DECLARE_TYPE(QBoxPlotModelMapper) QML_DECLARE_TYPE(QHBoxPlotModelMapper) QML_DECLARE_TYPE(QVBoxPlotModelMapper) +QML_DECLARE_TYPE(QCandlestickModelMapper) +QML_DECLARE_TYPE(QHCandlestickModelMapper) +QML_DECLARE_TYPE(QVCandlestickModelMapper) QML_DECLARE_TYPE(QAbstractSeries) QML_DECLARE_TYPE(QXYSeries) @@ -325,6 +334,14 @@ public: qmlRegisterType(uri, 2, 1, "ScatterSeries"); qmlRegisterType(uri, 2, 1, "LineSeries"); qmlRegisterType(uri, 2, 1, "SplineSeries"); + + // QtCharts 2.2 + qmlRegisterType(uri, 2, 2, "CandlestickSeries"); + qmlRegisterType(uri, 2, 2, "CandlestickSet"); + qmlRegisterUncreatableType(uri, 2, 2, "CandlestickModelMapper", + QLatin1String("Trying to create uncreatable: CandlestickModelMapper.")); + qmlRegisterType(uri, 2, 2, "HCandlestickModelMapper"); + qmlRegisterType(uri, 2, 2, "VCandlestickModelMapper"); } }; diff --git a/src/chartsqml2/declarativecandlestickseries.cpp b/src/chartsqml2/declarativecandlestickseries.cpp new file mode 100644 index 0000000..6981e85 --- /dev/null +++ b/src/chartsqml2/declarativecandlestickseries.cpp @@ -0,0 +1,244 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include "declarativeaxes.h" +#include "declarativecandlestickseries.h" + +QT_CHARTS_BEGIN_NAMESPACE + +DeclarativeCandlestickSet::DeclarativeCandlestickSet(qreal timestamp, QObject *parent) + : QCandlestickSet(timestamp, parent) +{ + connect(this, SIGNAL(brushChanged()), this, SLOT(handleBrushChanged())); +} + +void DeclarativeCandlestickSet::setBrushFilename(const QString &brushFilename) +{ + QImage brushImage(brushFilename); + if (QCandlestickSet::brush().textureImage() != brushImage) { + QBrush brush = QCandlestickSet::brush(); + brush.setTextureImage(brushImage); + + QCandlestickSet::setBrush(brush); + + m_brushFilename = brushFilename; + m_brushImage = brushImage; + + emit brushFilenameChanged(brushFilename); + } +} + +QString DeclarativeCandlestickSet::brushFilename() const +{ + return m_brushFilename; +} + +void DeclarativeCandlestickSet::handleBrushChanged() +{ + // If the texture image of the brush has changed along the brush + // the brush file name needs to be cleared. + if (!m_brushFilename.isEmpty() && QCandlestickSet::brush().textureImage() != m_brushImage) { + m_brushFilename.clear(); + emit brushFilenameChanged(QString()); + } +} + +// Declarative candlestick series ================================================================== + +DeclarativeCandlestickSeries::DeclarativeCandlestickSeries(QQuickItem *parent) + : QCandlestickSeries(parent), + m_axes(new DeclarativeAxes(this)) +{ + connect(m_axes, SIGNAL(axisXChanged(QAbstractAxis*)), + this, SIGNAL(axisXChanged(QAbstractAxis*))); + connect(m_axes, SIGNAL(axisYChanged(QAbstractAxis*)), + this, SIGNAL(axisYChanged(QAbstractAxis*))); + connect(m_axes, SIGNAL(axisXTopChanged(QAbstractAxis*)), + this, SIGNAL(axisXTopChanged(QAbstractAxis*))); + connect(m_axes, SIGNAL(axisYRightChanged(QAbstractAxis*)), + this, SIGNAL(axisYRightChanged(QAbstractAxis*))); + + connect(this, SIGNAL(hovered(bool, QCandlestickSet *)), + this, SLOT(onHovered(bool, QCandlestickSet *))); + connect(this, SIGNAL(clicked(QCandlestickSet *)), this, SLOT(onClicked(QCandlestickSet *))); + connect(this, SIGNAL(pressed(QCandlestickSet *)), this, SLOT(onPressed(QCandlestickSet *))); + connect(this, SIGNAL(released(QCandlestickSet *)), this, SLOT(onReleased(QCandlestickSet *))); + connect(this, SIGNAL(doubleClicked(QCandlestickSet *)), + this, SLOT(onDoubleClicked(QCandlestickSet *))); + + connect(this, SIGNAL(brushChanged()), this, SLOT(handleBrushChanged())); +} + +QQmlListProperty DeclarativeCandlestickSeries::seriesChildren() +{ + return QQmlListProperty(this, 0, &DeclarativeCandlestickSeries::appendSeriesChildren, + 0, 0, 0); +} + +void DeclarativeCandlestickSeries::setBrushFilename(const QString &brushFilename) +{ + QImage brushImage(brushFilename); + if (QCandlestickSeries::brush().textureImage() != brushImage) { + QBrush brush = QCandlestickSeries::brush(); + brush.setTextureImage(brushImage); + + QCandlestickSeries::setBrush(brush); + + m_brushFilename = brushFilename; + m_brushImage = brushImage; + + emit brushFilenameChanged(brushFilename); + } +} + +QString DeclarativeCandlestickSeries::brushFilename() const +{ + return m_brushFilename; +} + +DeclarativeCandlestickSet *DeclarativeCandlestickSeries::at(int index) +{ + QList sets = candlestickSets(); + if (index >= 0 && index < sets.count()) + return qobject_cast(sets[index]); + + return 0; +} + +bool DeclarativeCandlestickSeries::append(DeclarativeCandlestickSet *set) +{ + return QCandlestickSeries::append(qobject_cast(set)); +} + +bool DeclarativeCandlestickSeries::remove(DeclarativeCandlestickSet *set) +{ + return QCandlestickSeries::remove(qobject_cast(set)); +} + +bool DeclarativeCandlestickSeries::append(qreal open, qreal high, qreal low, qreal close, + qreal timestamp) +{ + QCandlestickSet *set = new QCandlestickSet(open, high, low, close, timestamp); + if (!QCandlestickSeries::append(set)) { + delete set; + return false; + } + + return true; +} + +bool DeclarativeCandlestickSeries::remove(qreal timestamp) +{ + for (int i = 0; i < count(); ++i) { + QCandlestickSet *set = candlestickSets().at(i); + if (set->timestamp() == timestamp) + return QCandlestickSeries::remove(set); + } + + return false; +} + +bool DeclarativeCandlestickSeries::insert(int index, DeclarativeCandlestickSet *set) +{ + return QCandlestickSeries::insert(index, qobject_cast(set)); +} + +void DeclarativeCandlestickSeries::clear() +{ + QCandlestickSeries::clear(); +} + +void DeclarativeCandlestickSeries::classBegin() +{ + // do nothing +} + +void DeclarativeCandlestickSeries::componentComplete() +{ + foreach (QObject *child, children()) { + if (qobject_cast(child)) { + QCandlestickSeries::append(qobject_cast(child)); + } else if (qobject_cast(child)) { + QHCandlestickModelMapper *mapper = qobject_cast(child); + mapper->setSeries(this); + } else if (qobject_cast(child)) { + QVCandlestickModelMapper *mapper = qobject_cast(child); + mapper->setSeries(this); + } // else: do nothing + } +} + +void DeclarativeCandlestickSeries::appendSeriesChildren(QQmlListProperty *list, + QObject *element) +{ + // Empty implementation; the children are parsed in componentComplete instead + Q_UNUSED(list); + Q_UNUSED(element); +} + +void DeclarativeCandlestickSeries::onClicked(QCandlestickSet *set) +{ + emit clicked(qobject_cast(set)); +} + +void DeclarativeCandlestickSeries::onHovered(bool status, QCandlestickSet *set) +{ + emit hovered(status, qobject_cast(set)); +} + +void DeclarativeCandlestickSeries::onPressed(QCandlestickSet *set) +{ + emit pressed(qobject_cast(set)); +} + +void DeclarativeCandlestickSeries::onReleased(QCandlestickSet *set) +{ + emit released(qobject_cast(set)); +} + +void DeclarativeCandlestickSeries::onDoubleClicked(QCandlestickSet *set) +{ + emit doubleClicked(qobject_cast(set)); +} + +void DeclarativeCandlestickSeries::handleBrushChanged() +{ + // If the texture image of the brush has changed along the brush + // the brush file name needs to be cleared. + if (!m_brushFilename.isEmpty() && QCandlestickSeries::brush().textureImage() != m_brushImage) { + m_brushFilename.clear(); + emit brushFilenameChanged(QString()); + } +} + +#include "moc_declarativecandlestickseries.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/chartsqml2/declarativecandlestickseries.h b/src/chartsqml2/declarativecandlestickseries.h new file mode 100644 index 0000000..9b7c0e3 --- /dev/null +++ b/src/chartsqml2/declarativecandlestickseries.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef DECLARATIVECANDLESTICKSERIES_H +#define DECLARATIVECANDLESTICKSERIES_H + +#include +#include +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE + +class DeclarativeAxes; +class QAbstractAxis; + +class DeclarativeCandlestickSet : public QCandlestickSet +{ + Q_OBJECT + Q_PROPERTY(QString brushFilename READ brushFilename WRITE setBrushFilename NOTIFY brushFilenameChanged) + +public: + explicit DeclarativeCandlestickSet(qreal timestamp = 0.0, QObject *parent = nullptr); + void setBrushFilename(const QString &brushFilename); + QString brushFilename() const; + +Q_SIGNALS: + void brushFilenameChanged(const QString &brushFilename); + +private Q_SLOTS: + void handleBrushChanged(); + +private: + QString m_brushFilename; + QImage m_brushImage; +}; + +class DeclarativeCandlestickSeries : public QCandlestickSeries, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QAbstractAxis *axisX READ axisX WRITE setAxisX NOTIFY axisXChanged) + Q_PROPERTY(QAbstractAxis *axisY READ axisY WRITE setAxisY NOTIFY axisYChanged) + Q_PROPERTY(QAbstractAxis *axisXTop READ axisXTop WRITE setAxisXTop NOTIFY axisXTopChanged) + Q_PROPERTY(QAbstractAxis *axisYRight READ axisYRight WRITE setAxisYRight NOTIFY axisYRightChanged) + Q_PROPERTY(QQmlListProperty seriesChildren READ seriesChildren) + Q_PROPERTY(QString brushFilename READ brushFilename WRITE setBrushFilename NOTIFY brushFilenameChanged) + Q_CLASSINFO("DefaultProperty", "seriesChildren") + +public: + explicit DeclarativeCandlestickSeries(QQuickItem *parent = nullptr); + void setAxisX(QAbstractAxis *axis) { m_axes->setAxisX(axis); } + QAbstractAxis *axisX() { return m_axes->axisX(); } + void setAxisY(QAbstractAxis *axis) { m_axes->setAxisY(axis); } + QAbstractAxis *axisY() { return m_axes->axisY(); } + void setAxisXTop(QAbstractAxis *axis) { m_axes->setAxisXTop(axis); } + QAbstractAxis *axisXTop() { return m_axes->axisXTop(); } + void setAxisYRight(QAbstractAxis *axis) { m_axes->setAxisYRight(axis); } + QAbstractAxis *axisYRight() { return m_axes->axisYRight(); } + QQmlListProperty seriesChildren(); + void setBrushFilename(const QString &brushFilename); + QString brushFilename() const; + +public: + Q_INVOKABLE DeclarativeCandlestickSet *at(int index); + Q_INVOKABLE bool append(DeclarativeCandlestickSet *set); + Q_INVOKABLE bool remove(DeclarativeCandlestickSet *set); + Q_INVOKABLE bool append(qreal open, qreal high, qreal low, qreal close, qreal timestamp); + Q_INVOKABLE bool remove(qreal timestamp); + Q_INVOKABLE bool insert(int index, DeclarativeCandlestickSet *set); + Q_INVOKABLE void clear(); + +public: // from QDeclarativeParserStatus + void classBegin(); + void componentComplete(); + +Q_SIGNALS: + void axisXChanged(QAbstractAxis *axis); + void axisYChanged(QAbstractAxis *axis); + void axisXTopChanged(QAbstractAxis *axis); + void axisYRightChanged(QAbstractAxis *axis); + void clicked(DeclarativeCandlestickSet *set); + void hovered(bool status, DeclarativeCandlestickSet *set); + void pressed(DeclarativeCandlestickSet *set); + void released(DeclarativeCandlestickSet *set); + void doubleClicked(DeclarativeCandlestickSet *set); + void brushFilenameChanged(const QString &brushFilename); + +public Q_SLOTS: + static void appendSeriesChildren(QQmlListProperty *list, QObject *element); + void onClicked(QCandlestickSet *set); + void onHovered(bool status, QCandlestickSet *set); + void onPressed(QCandlestickSet *set); + void onReleased(QCandlestickSet *set); + void onDoubleClicked(QCandlestickSet *set); + +private Q_SLOTS: + void handleBrushChanged(); + +public: + DeclarativeAxes *m_axes; + +private: + QString m_brushFilename; + QImage m_brushImage; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // DECLARATIVECANDLESTICKSERIES_H diff --git a/src/chartsqml2/declarativechart.cpp b/src/chartsqml2/declarativechart.cpp index ec5e83a..66b4772 100644 --- a/src/chartsqml2/declarativechart.cpp +++ b/src/chartsqml2/declarativechart.cpp @@ -35,6 +35,7 @@ #include "declarativepieseries.h" #include "declarativesplineseries.h" #include "declarativeboxplotseries.h" +#include "declarativecandlestickseries.h" #include "declarativescatterseries.h" #include "declarativechartnode.h" #include "declarativerendernode.h" @@ -1077,6 +1078,9 @@ QAbstractSeries *DeclarativeChart::createSeries(int type, QString name, QAbstrac case DeclarativeChart::SeriesTypeBoxPlot: series = new DeclarativeBoxPlotSeries(); break; + case DeclarativeChart::SeriesTypeCandlestick: + series = new DeclarativeCandlestickSeries(); + break; case DeclarativeChart::SeriesTypePie: series = new DeclarativePieSeries(); break; @@ -1189,6 +1193,8 @@ void DeclarativeChart::initializeAxes(QAbstractSeries *series) doInitializeAxes(series, qobject_cast(series)->m_axes); else if (qobject_cast(series)) doInitializeAxes(series, qobject_cast(series)->m_axes); + else if (qobject_cast(series)) + doInitializeAxes(series, qobject_cast(series)->m_axes); // else: do nothing } diff --git a/src/chartsqml2/declarativechart.h b/src/chartsqml2/declarativechart.h index 2a37d8e..dd58b54 100644 --- a/src/chartsqml2/declarativechart.h +++ b/src/chartsqml2/declarativechart.h @@ -97,6 +97,7 @@ public: SeriesTypeStackedBar, SeriesTypePercentBar, SeriesTypeBoxPlot, + SeriesTypeCandlestick, SeriesTypePie, SeriesTypeScatter, SeriesTypeSpline, diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index e8cad75..1b04ac7 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -23,7 +23,10 @@ SUBDIRS += \ domain \ chartdataset \ qlegend \ - cmake + cmake \ + qcandlestickmodelmapper \ + qcandlestickseries \ + qcandlestickset !contains(QT_COORD_TYPE, float): { SUBDIRS += \ diff --git a/tests/auto/qcandlestickmodelmapper/qcandlestickmodelmapper.pro b/tests/auto/qcandlestickmodelmapper/qcandlestickmodelmapper.pro new file mode 100644 index 0000000..81f0b36 --- /dev/null +++ b/tests/auto/qcandlestickmodelmapper/qcandlestickmodelmapper.pro @@ -0,0 +1,5 @@ +!include( ../auto.pri ) { + error( "Couldn't find the auto.pri file!" ) +} + +SOURCES += tst_qcandlestickmodelmapper.cpp diff --git a/tests/auto/qcandlestickmodelmapper/tst_qcandlestickmodelmapper.cpp b/tests/auto/qcandlestickmodelmapper/tst_qcandlestickmodelmapper.cpp new file mode 100644 index 0000000..b6110c7 --- /dev/null +++ b/tests/auto/qcandlestickmodelmapper/tst_qcandlestickmodelmapper.cpp @@ -0,0 +1,632 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_CHARTS_USE_NAMESPACE + +class tst_qcandlestickmodelmapper : public QObject +{ + Q_OBJECT + +public: + tst_qcandlestickmodelmapper(); + + void createVerticalMapper(); + void createHorizontalMapper(); + +public Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + +private Q_SLOTS: + void verticalMapper_data(); + void verticalMapper(); + void verticalMapperCustomMapping_data(); + void verticalMapperCustomMapping(); + void horizontalMapper_data(); + void horizontalMapper(); + void horizontalMapperCustomMapping_data(); + void horizontalMapperCustomMapping(); + void seriesUpdated(); + void verticalModelInsertRows(); + void verticalModelRemoveRows(); + void verticalModelInsertColumns(); + void verticalModelRemoveColumns(); + void horizontalModelInsertRows(); + void horizontalModelRemoveRows(); + void horizontalModelInsertColumns(); + void horizontalModelRemoveColumns(); + void modelUpdateCell(); + void verticalMapperSignals(); + void horizontalMapperSignals(); + +private: + QStandardItemModel *m_model; + int m_modelRowCount; + int m_modelColumnCount; + + QCandlestickSeries *m_series; + QChart *m_chart; + QChartView *m_chartView; + + QHCandlestickModelMapper *m_hMapper; + QVCandlestickModelMapper *m_vMapper; +}; + +tst_qcandlestickmodelmapper::tst_qcandlestickmodelmapper() + : m_model(nullptr), + m_modelRowCount(10), + m_modelColumnCount(8), + m_series(nullptr), + m_chart(nullptr), + m_chartView(nullptr), + m_hMapper(nullptr), + m_vMapper(nullptr) +{ +} + +void tst_qcandlestickmodelmapper::createHorizontalMapper() +{ + m_hMapper = new QHCandlestickModelMapper; + QVERIFY(m_hMapper->model() == nullptr); + m_hMapper->setTimestampColumn(0); + m_hMapper->setOpenColumn(1); + m_hMapper->setHighColumn(3); + m_hMapper->setLowColumn(5); + m_hMapper->setCloseColumn(6); + m_hMapper->setFirstCandlestickSetRow(0); + m_hMapper->setLastCandlestickSetRow(4); + m_hMapper->setModel(m_model); + m_hMapper->setSeries(m_series); +} + +void tst_qcandlestickmodelmapper::createVerticalMapper() +{ + m_vMapper = new QVCandlestickModelMapper; + QVERIFY(m_vMapper->model() == nullptr); + m_vMapper->setTimestampRow(0); + m_vMapper->setOpenRow(1); + m_vMapper->setHighRow(3); + m_vMapper->setLowRow(5); + m_vMapper->setCloseRow(6); + m_vMapper->setFirstCandlestickSetColumn(0); + m_vMapper->setLastCandlestickSetColumn(4); + m_vMapper->setModel(m_model); + m_vMapper->setSeries(m_series); +} + +void tst_qcandlestickmodelmapper::initTestCase() +{ + m_chart = new QChart(); + m_chartView = new QChartView(m_chart); + m_chartView->show(); +} + +void tst_qcandlestickmodelmapper::cleanupTestCase() +{ + delete m_chartView; + QTest::qWait(1); // Allow final deleteLaters to run +} + +void tst_qcandlestickmodelmapper::init() +{ + m_series = new QCandlestickSeries(); + m_chart->addSeries(m_series); + + m_model = new QStandardItemModel(m_modelRowCount, m_modelColumnCount, this); + for (int row = 0; row < m_modelRowCount; ++row) { + for (int column = 0; column < m_modelColumnCount; ++column) + m_model->setData(m_model->index(row, column), row * column); + } +} + +void tst_qcandlestickmodelmapper::cleanup() +{ + m_chart->removeSeries(m_series); + delete m_series; + m_series = nullptr; + + m_model->clear(); + m_model->deleteLater(); + m_model = nullptr; + + if (m_vMapper) { + m_vMapper->deleteLater(); + m_vMapper = nullptr; + } + + if (m_hMapper) { + m_hMapper->deleteLater(); + m_hMapper = nullptr; + } +} + +void tst_qcandlestickmodelmapper::verticalMapper_data() +{ + QTest::addColumn("firstCandlestickSetColumn"); + QTest::addColumn("lastCandlestickSetColumn"); + QTest::addColumn("expectedCandlestickSetCount"); + + QTest::newRow("last column greater than first column") << 0 << 1 << 2; + QTest::newRow("last column equal to first column") << 1 << 1 << 1; + QTest::newRow("last column lesser than first column") << 1 << 0 << 0; + QTest::newRow("invalid first column and correct last column") << -3 << 1 << 0; + QTest::newRow("first column beyond the size of model and correct last column") << m_modelColumnCount << 1 << 0; + QTest::newRow("first column beyond the size of model and invalid last column") << m_modelColumnCount << -1 << 0; +} + +void tst_qcandlestickmodelmapper::verticalMapper() +{ + QFETCH(int, firstCandlestickSetColumn); + QFETCH(int, lastCandlestickSetColumn); + QFETCH(int, expectedCandlestickSetCount); + + QCandlestickSeries *series = new QCandlestickSeries(); + m_chart->addSeries(series); + + createVerticalMapper(); + m_vMapper->setFirstCandlestickSetColumn(firstCandlestickSetColumn); + m_vMapper->setLastCandlestickSetColumn(lastCandlestickSetColumn); + m_vMapper->setSeries(series); + + QCOMPARE(m_vMapper->firstCandlestickSetColumn(), qMax(firstCandlestickSetColumn, -1)); + QCOMPARE(m_vMapper->lastCandlestickSetColumn(), qMax(lastCandlestickSetColumn, -1)); + QCOMPARE(series->count(), expectedCandlestickSetCount); + + m_chart->removeSeries(series); + delete series; +} + +void tst_qcandlestickmodelmapper::verticalMapperCustomMapping_data() +{ + QTest::addColumn("timestampRow"); + QTest::addColumn("openRow"); + QTest::addColumn("highRow"); + QTest::addColumn("lowRow"); + QTest::addColumn("closeRow"); + + QTest::newRow("all rows are correct") << 0 << 1 << 2 << 3 << 4; + QTest::newRow("all rows are invalid") << -3 << -3 << -3 << -3 << -3; + QTest::newRow("timestamp: -1 (invalid)") << -1 << 1 << 2 << 3 << 4; + QTest::newRow("timestamp: -3 (invalid - should default to -1)") << -3 << 1 << 2 << 3 << 4; + QTest::newRow("timestamp: +1 greater than the number of rows in the model") << m_modelRowCount + 1 << 1 << 2 << 3 << 4; + QTest::newRow("open: -1 (invalid)") << 0 << -1 << 2 << 3 << 4; + QTest::newRow("open: -3 (invalid - should default to -1)") << 0 << -3 << 2 << 3 << 4; + QTest::newRow("open: +1 greater than the number of rows in the model") << 0 << m_modelRowCount + 1 << 2 << 3 << 4; + QTest::newRow("high: -1 (invalid)") << 0 << 1 << -1 << 3 << 4; + QTest::newRow("high: -3 (invalid - should default to -1)") << 0 << 1 << -3 << 3 << 4; + QTest::newRow("high: +1 greater than the number of rows in the model") << 0 << 1 << m_modelRowCount + 1 << 3 << 4; + QTest::newRow("low: -1 (invalid)") << 0 << 1 << 2 << -1 << 4; + QTest::newRow("low: -3 (invalid - should default to -1)") << 0 << 1 << 2 << -3 << 4; + QTest::newRow("low: +1 greater than the number of rows in the model") << 0 << 1 << 2 << m_modelRowCount + 1 << 4; + QTest::newRow("close: -1 (invalid)") << 0 << 1 << 2 << 3 << -1; + QTest::newRow("close: -3 (invalid - should default to -1)") << 0 << 1 << 2 << 3 << -3; + QTest::newRow("close: +1 greater than the number of rows in the model") << 0 << 1 << 2 << 3 << m_modelRowCount + 1; +} + +void tst_qcandlestickmodelmapper::verticalMapperCustomMapping() +{ + QFETCH(int, timestampRow); + QFETCH(int, openRow); + QFETCH(int, highRow); + QFETCH(int, lowRow); + QFETCH(int, closeRow); + + QCandlestickSeries *series = new QCandlestickSeries(); + m_chart->addSeries(series); + QCOMPARE(series->count(), 0); + + createVerticalMapper(); + m_vMapper->setTimestampRow(timestampRow); + m_vMapper->setOpenRow(openRow); + m_vMapper->setHighRow(highRow); + m_vMapper->setLowRow(lowRow); + m_vMapper->setCloseRow(closeRow); + m_vMapper->setSeries(series); + + QCOMPARE(m_vMapper->timestampRow(), qMax(timestampRow, -1)); + QCOMPARE(m_vMapper->openRow(), qMax(openRow, -1)); + QCOMPARE(m_vMapper->highRow(), qMax(highRow, -1)); + QCOMPARE(m_vMapper->lowRow(), qMax(lowRow, -1)); + QCOMPARE(m_vMapper->closeRow(), qMax(closeRow, -1)); + + int count; + if ((m_vMapper->timestampRow() >= 0 && m_vMapper->timestampRow() < m_modelRowCount) + && (m_vMapper->openRow() >= 0 && m_vMapper->openRow() < m_modelRowCount) + && (m_vMapper->highRow() >= 0 && m_vMapper->highRow() < m_modelRowCount) + && (m_vMapper->lowRow() >= 0 && m_vMapper->lowRow() < m_modelRowCount) + && (m_vMapper->closeRow() >= 0 && m_vMapper->closeRow() < m_modelRowCount)) + count = m_vMapper->lastCandlestickSetColumn() - m_vMapper->firstCandlestickSetColumn() + 1; + else + count = 0; + QCOMPARE(series->count(), count); + + // change values column mapping to invalid + m_vMapper->setFirstCandlestickSetColumn(-1); + m_vMapper->setLastCandlestickSetColumn(1); + QCOMPARE(series->count(), 0); + + m_chart->removeSeries(series); + delete series; +} + +void tst_qcandlestickmodelmapper::horizontalMapper_data() +{ + QTest::addColumn("firstCandlestickSetRow"); + QTest::addColumn("lastCandlestickSetRow"); + QTest::addColumn("expectedCandlestickSetCount"); + + QTest::newRow("last row greater than first row") << 0 << 1 << 2; + QTest::newRow("last row equal to first row") << 1 << 1 << 1; + QTest::newRow("last row lesser than first row") << 1 << 0 << 0; + QTest::newRow("invalid first row and correct last row") << -3 << 1 << 0; + QTest::newRow("first row beyond the size of model and correct last row") << m_modelRowCount << 1 << 0; + QTest::newRow("first row beyond the size of model and invalid last row") << m_modelRowCount << -1 << 0; +} + +void tst_qcandlestickmodelmapper::horizontalMapper() +{ + QFETCH(int, firstCandlestickSetRow); + QFETCH(int, lastCandlestickSetRow); + QFETCH(int, expectedCandlestickSetCount); + + QCandlestickSeries *series = new QCandlestickSeries(); + m_chart->addSeries(series); + + createHorizontalMapper(); + m_hMapper->setFirstCandlestickSetRow(firstCandlestickSetRow); + m_hMapper->setLastCandlestickSetRow(lastCandlestickSetRow); + m_hMapper->setSeries(series); + + QCOMPARE(m_hMapper->firstCandlestickSetRow(), qMax(firstCandlestickSetRow, -1)); + QCOMPARE(m_hMapper->lastCandlestickSetRow(), qMax(lastCandlestickSetRow, -1)); + QCOMPARE(series->count(), expectedCandlestickSetCount); + + m_chart->removeSeries(series); + delete series; +} + +void tst_qcandlestickmodelmapper::horizontalMapperCustomMapping_data() +{ + QTest::addColumn("timestampColumn"); + QTest::addColumn("openColumn"); + QTest::addColumn("highColumn"); + QTest::addColumn("lowColumn"); + QTest::addColumn("closeColumn"); + + QTest::newRow("all columns are correct") << 0 << 1 << 2 << 3 << 4; + QTest::newRow("all columns are invalid") << -3 << -3 << -3 << -3 << -3; + QTest::newRow("timestamp: -1 (invalid)") << -1 << 1 << 2 << 3 << 4; + QTest::newRow("timestamp: -3 (invalid - should default to -1)") << -3 << 1 << 2 << 3 << 4; + QTest::newRow("timestamp: +1 greater than the number of columns in the model") << m_modelColumnCount + 1 << 1 << 2 << 3 << 4; + QTest::newRow("open: -1 (invalid)") << 0 << -1 << 2 << 3 << 4; + QTest::newRow("open: -3 (invalid - should default to -1)") << 0 << -3 << 2 << 3 << 4; + QTest::newRow("open: +1 greater than the number of columns in the model") << 0 << m_modelColumnCount + 1 << 2 << 3 << 4; + QTest::newRow("high: -1 (invalid)") << 0 << 1 << -1 << 3 << 4; + QTest::newRow("high: -3 (invalid - should default to -1)") << 0 << 1 << -3 << 3 << 4; + QTest::newRow("high: +1 greater than the number of columns in the model") << 0 << 1 << m_modelColumnCount + 1 << 3 << 4; + QTest::newRow("low: -1 (invalid)") << 0 << 1 << 2 << -1 << 4; + QTest::newRow("low: -3 (invalid - should default to -1)") << 0 << 1 << 2 << -3 << 4; + QTest::newRow("low: +1 greater than the number of columns in the model") << 0 << 1 << 2 << m_modelColumnCount + 1 << 4; + QTest::newRow("close: -1 (invalid)") << 0 << 1 << 2 << 3 << -1; + QTest::newRow("close: -3 (invalid - should default to -1)") << 0 << 1 << 2 << 3 << -3; + QTest::newRow("close: +1 greater than the number of columns in the model") << 0 << 1 << 2 << 3 << m_modelColumnCount + 1; +} + +void tst_qcandlestickmodelmapper::horizontalMapperCustomMapping() +{ + QFETCH(int, timestampColumn); + QFETCH(int, openColumn); + QFETCH(int, highColumn); + QFETCH(int, lowColumn); + QFETCH(int, closeColumn); + + QCandlestickSeries *series = new QCandlestickSeries(); + m_chart->addSeries(series); + QCOMPARE(series->count(), 0); + + createHorizontalMapper(); + m_hMapper->setTimestampColumn(timestampColumn); + m_hMapper->setOpenColumn(openColumn); + m_hMapper->setHighColumn(highColumn); + m_hMapper->setLowColumn(lowColumn); + m_hMapper->setCloseColumn(closeColumn); + m_hMapper->setSeries(series); + + QCOMPARE(m_hMapper->timestampColumn(), qMax(timestampColumn, -1)); + QCOMPARE(m_hMapper->openColumn(), qMax(openColumn, -1)); + QCOMPARE(m_hMapper->highColumn(), qMax(highColumn, -1)); + QCOMPARE(m_hMapper->lowColumn(), qMax(lowColumn, -1)); + QCOMPARE(m_hMapper->closeColumn(), qMax(closeColumn, -1)); + + int count; + if ((m_hMapper->timestampColumn() >= 0 && m_hMapper->timestampColumn() < m_modelColumnCount) + && (m_hMapper->openColumn() >= 0 && m_hMapper->openColumn() < m_modelColumnCount) + && (m_hMapper->highColumn() >= 0 && m_hMapper->highColumn() < m_modelColumnCount) + && (m_hMapper->lowColumn() >= 0 && m_hMapper->lowColumn() < m_modelColumnCount) + && (m_hMapper->closeColumn() >= 0 && m_hMapper->closeColumn() < m_modelColumnCount)) + count = m_hMapper->lastCandlestickSetRow() - m_hMapper->firstCandlestickSetRow() + 1; + else + count = 0; + QCOMPARE(series->count(), count); + + // change values row mapping to invalid + m_hMapper->setFirstCandlestickSetRow(-1); + m_hMapper->setLastCandlestickSetRow(1); + QCOMPARE(series->count(), 0); + + m_chart->removeSeries(series); + delete series; +} + +void tst_qcandlestickmodelmapper::seriesUpdated() +{ + createVerticalMapper(); + QVERIFY(m_vMapper->model() != nullptr); + + QCandlestickSet *set = m_series->candlestickSets().value(0, 0); + QVERIFY(set != nullptr); + + // update values + QCOMPARE(m_model->data(m_model->index(m_vMapper->timestampRow(), 0)).toReal(),set->timestamp()); + QCOMPARE(m_model->data(m_model->index(m_vMapper->openRow(), 0)).toReal(), set->open()); + QCOMPARE(m_model->data(m_model->index(m_vMapper->highRow(), 0)).toReal(), set->high()); + QCOMPARE(m_model->data(m_model->index(m_vMapper->lowRow(), 0)).toReal(), set->low()); + QCOMPARE(m_model->data(m_model->index(m_vMapper->closeRow(), 0)).toReal(), set->close()); + set->setTimestamp(set->timestamp() + 5.0); + set->setOpen(set->open() + 6.0); + set->setHigh(set->high() + 7.0); + set->setLow(set->low() + 8.0); + set->setClose(set->close() + 9.0); + QCOMPARE(m_model->data(m_model->index(m_vMapper->timestampRow(), 0)).toReal(),set->timestamp()); + QCOMPARE(m_model->data(m_model->index(m_vMapper->openRow(), 0)).toReal(), set->open()); + QCOMPARE(m_model->data(m_model->index(m_vMapper->highRow(), 0)).toReal(), set->high()); + QCOMPARE(m_model->data(m_model->index(m_vMapper->lowRow(), 0)).toReal(), set->low()); + QCOMPARE(m_model->data(m_model->index(m_vMapper->closeRow(), 0)).toReal(), set->close()); + + // append new sets + QList newCandlestickSets; + newCandlestickSets << new QCandlestickSet(3.0, 5.0, 2.0, 4.0, 1234); + newCandlestickSets << new QCandlestickSet(5.0, 7.0, 4.0, 6.0, 5678); + m_series->append(newCandlestickSets); + QCOMPARE(m_model->columnCount(), m_modelColumnCount + newCandlestickSets.count()); + + // remove sets + newCandlestickSets.clear(); + newCandlestickSets << m_series->candlestickSets().at(m_series->count() - 1); + newCandlestickSets << m_series->candlestickSets().at(m_series->count() - 2); + m_series->remove(newCandlestickSets); + QCOMPARE(m_model->columnCount(), m_modelColumnCount); +} + +void tst_qcandlestickmodelmapper::verticalModelInsertRows() +{ + createVerticalMapper(); + int count = m_vMapper->lastCandlestickSetColumn() - m_vMapper->firstCandlestickSetColumn() + 1; + QVERIFY(m_vMapper->model() != 0); + QCOMPARE(m_series->count(), count); + + m_model->insertRows(3, 4); + QCOMPARE(m_series->count(), count); +} + +void tst_qcandlestickmodelmapper::verticalModelRemoveRows() +{ + createVerticalMapper(); + int count = m_vMapper->lastCandlestickSetColumn() - m_vMapper->firstCandlestickSetColumn() + 1; + QVERIFY(m_vMapper->model() != 0); + QCOMPARE(m_series->count(), count); + + m_model->removeRows(m_modelRowCount - 1, 1); + QCOMPARE(m_series->count(), count); + + int removeCount = m_model->rowCount() - m_vMapper->closeRow(); + m_model->removeRows(m_vMapper->closeRow(), removeCount); + QCOMPARE(m_series->count(), 0); +} + +void tst_qcandlestickmodelmapper::verticalModelInsertColumns() +{ + createVerticalMapper(); + int count = m_vMapper->lastCandlestickSetColumn() - m_vMapper->firstCandlestickSetColumn() + 1; + QVERIFY(m_vMapper->model() != 0); + QCOMPARE(m_series->count(), count); + + m_model->insertColumns(3, 4); + QCOMPARE(m_series->count(), count); +} + +void tst_qcandlestickmodelmapper::verticalModelRemoveColumns() +{ + createVerticalMapper(); + int count = m_vMapper->lastCandlestickSetColumn() - m_vMapper->firstCandlestickSetColumn() + 1; + QVERIFY(m_vMapper->model() != 0); + QCOMPARE(m_series->count(), count); + + int removeCount = m_modelColumnCount - 2; + m_model->removeColumns(0, removeCount); + QCOMPARE(m_series->count(), qMin(m_model->columnCount(), count)); + + // leave all the columns + m_model->removeColumns(0, m_modelColumnCount - removeCount); + QCOMPARE(m_series->count(), 0); +} + +void tst_qcandlestickmodelmapper::horizontalModelInsertRows() +{ + createHorizontalMapper(); + int count = m_hMapper->lastCandlestickSetRow() - m_hMapper->firstCandlestickSetRow() + 1; + QVERIFY(m_hMapper->model() != 0); + QCOMPARE(m_series->count(), count); + + m_model->insertRows(3, 4); + QCOMPARE(m_series->count(), count); +} + +void tst_qcandlestickmodelmapper::horizontalModelRemoveRows() +{ + createHorizontalMapper(); + int count = m_hMapper->lastCandlestickSetRow() - m_hMapper->firstCandlestickSetRow() + 1; + QVERIFY(m_hMapper->model() != 0); + QCOMPARE(m_series->count(), qMin(m_model->rowCount(), count)); + + int removeCount = m_modelRowCount - 2; + m_model->removeRows(0, removeCount); + QCOMPARE(m_series->count(), qMin(m_model->rowCount(), count)); + + // leave all the columns + m_model->removeRows(0, m_modelRowCount - removeCount); + QCOMPARE(m_series->count(), 0); +} + +void tst_qcandlestickmodelmapper::horizontalModelInsertColumns() +{ + createHorizontalMapper(); + int count = m_hMapper->lastCandlestickSetRow() - m_hMapper->firstCandlestickSetRow() + 1; + QVERIFY(m_hMapper->model() != 0); + QCOMPARE(m_series->count(), count); + + m_model->insertColumns(3, 4); + QCOMPARE(m_series->count(), count); +} + +void tst_qcandlestickmodelmapper::horizontalModelRemoveColumns() +{ + createHorizontalMapper(); + int count = m_hMapper->lastCandlestickSetRow() - m_hMapper->firstCandlestickSetRow() + 1; + QVERIFY(m_hMapper->model() != 0); + QCOMPARE(m_series->count(), count); + + m_model->removeColumns(m_modelColumnCount - 1, 1); + QCOMPARE(m_series->count(), count); + + int removeCount = m_model->columnCount() - m_hMapper->closeColumn(); + m_model->removeColumns(m_hMapper->closeColumn(), removeCount); + QCOMPARE(m_series->count(), 0); +} + +void tst_qcandlestickmodelmapper::modelUpdateCell() +{ + createVerticalMapper(); + QVERIFY(m_vMapper->model() != 0); + + QModelIndex index = m_model->index(m_vMapper->timestampRow(), 0); + qreal newValue = 44.0; + QVERIFY(m_model->setData(index, newValue)); + QCOMPARE(m_model->data(index).toReal(), newValue); + QCOMPARE(m_series->candlestickSets().at(index.row())->timestamp(), newValue); +} + +void tst_qcandlestickmodelmapper::verticalMapperSignals() +{ + QVCandlestickModelMapper *mapper = new QVCandlestickModelMapper(); + + QSignalSpy spy0(mapper, SIGNAL(modelReplaced())); + QSignalSpy spy1(mapper, SIGNAL(seriesReplaced())); + QSignalSpy spy2(mapper, SIGNAL(timestampRowChanged())); + QSignalSpy spy3(mapper, SIGNAL(openRowChanged())); + QSignalSpy spy4(mapper, SIGNAL(highRowChanged())); + QSignalSpy spy5(mapper, SIGNAL(lowRowChanged())); + QSignalSpy spy6(mapper, SIGNAL(closeRowChanged())); + QSignalSpy spy7(mapper, SIGNAL(firstCandlestickSetColumnChanged())); + QSignalSpy spy8(mapper, SIGNAL(lastCandlestickSetColumnChanged())); + + mapper->setModel(m_model); + mapper->setSeries(m_series); + mapper->setTimestampRow(1); + mapper->setOpenRow(2); + mapper->setHighRow(3); + mapper->setLowRow(4); + mapper->setCloseRow(5); + mapper->setFirstCandlestickSetColumn(0); + mapper->setLastCandlestickSetColumn(1); + + QCOMPARE(spy0.count(), 1); + QCOMPARE(spy1.count(), 1); + QCOMPARE(spy2.count(), 1); + QCOMPARE(spy3.count(), 1); + QCOMPARE(spy4.count(), 1); + QCOMPARE(spy5.count(), 1); + QCOMPARE(spy6.count(), 1); + QCOMPARE(spy7.count(), 1); + QCOMPARE(spy8.count(), 1); + + delete mapper; +} + +void tst_qcandlestickmodelmapper::horizontalMapperSignals() +{ + QHCandlestickModelMapper *mapper = new QHCandlestickModelMapper(); + + QSignalSpy spy0(mapper, SIGNAL(modelReplaced())); + QSignalSpy spy1(mapper, SIGNAL(seriesReplaced())); + QSignalSpy spy2(mapper, SIGNAL(timestampColumnChanged())); + QSignalSpy spy3(mapper, SIGNAL(openColumnChanged())); + QSignalSpy spy4(mapper, SIGNAL(highColumnChanged())); + QSignalSpy spy5(mapper, SIGNAL(lowColumnChanged())); + QSignalSpy spy6(mapper, SIGNAL(closeColumnChanged())); + QSignalSpy spy7(mapper, SIGNAL(firstCandlestickSetRowChanged())); + QSignalSpy spy8(mapper, SIGNAL(lastCandlestickSetRowChanged())); + + mapper->setModel(m_model); + mapper->setSeries(m_series); + mapper->setTimestampColumn(1); + mapper->setOpenColumn(2); + mapper->setHighColumn(3); + mapper->setLowColumn(4); + mapper->setCloseColumn(5); + mapper->setFirstCandlestickSetRow(0); + mapper->setLastCandlestickSetRow(1); + + QCOMPARE(spy0.count(), 1); + QCOMPARE(spy1.count(), 1); + QCOMPARE(spy2.count(), 1); + QCOMPARE(spy3.count(), 1); + QCOMPARE(spy4.count(), 1); + QCOMPARE(spy5.count(), 1); + QCOMPARE(spy6.count(), 1); + QCOMPARE(spy7.count(), 1); + QCOMPARE(spy8.count(), 1); + + delete mapper; +} + +QTEST_MAIN(tst_qcandlestickmodelmapper) + +#include "tst_qcandlestickmodelmapper.moc" diff --git a/tests/auto/qcandlestickseries/qcandlestickseries.pro b/tests/auto/qcandlestickseries/qcandlestickseries.pro new file mode 100644 index 0000000..ccaa2e2 --- /dev/null +++ b/tests/auto/qcandlestickseries/qcandlestickseries.pro @@ -0,0 +1,5 @@ +!include( ../auto.pri ) { + error( "Couldn't find the auto.pri file!" ) +} + +SOURCES += tst_qcandlestickseries.cpp diff --git a/tests/auto/qcandlestickseries/tst_qcandlestickseries.cpp b/tests/auto/qcandlestickseries/tst_qcandlestickseries.cpp new file mode 100644 index 0000000..61201bf --- /dev/null +++ b/tests/auto/qcandlestickseries/tst_qcandlestickseries.cpp @@ -0,0 +1,936 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include "tst_definitions.h" + +QT_CHARTS_USE_NAMESPACE + +Q_DECLARE_METATYPE(QCandlestickSet *) +Q_DECLARE_METATYPE(QList) + +class tst_QCandlestickSeries : public QObject +{ + Q_OBJECT + +public Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + +private Q_SLOTS: + void qCandlestickSeries(); + void append(); + void remove(); + void appendList(); + void removeList(); + void insert(); + void take(); + void clear(); + void candlestickSets(); + void count(); + void type(); + void maximumColumnWidth_data(); + void maximumColumnWidth(); + void minimumColumnWidth_data(); + void minimumColumnWidth(); + void bodyWidth_data(); + void bodyWidth(); + void bodyOutlineVisible(); + void capsWidth_data(); + void capsWidth(); + void capsVisible(); + void increasingColor(); + void decreasingColor(); + void brush(); + void pen(); + void mouseClicked(); + void mouseHovered(); + void mousePressed(); + void mouseReleased(); + void mouseDoubleClicked(); + +private: + QCandlestickSeries *m_series; + QList m_sets; +}; + +void tst_QCandlestickSeries::initTestCase() +{ + qRegisterMetaType("QCandlestickSet *"); + qRegisterMetaType>("QList"); +} + +void tst_QCandlestickSeries::cleanupTestCase() +{ + QTest::qWait(1); // Allow final deleteLaters to run +} + +void tst_QCandlestickSeries::init() +{ + m_series = new QCandlestickSeries(); + m_series->setMaximumColumnWidth(5432.1); + m_series->setMinimumColumnWidth(2.0); + m_series->setBodyWidth(0.99); + m_series->setCapsWidth(0.99); + + for (int i = 0; i < 5; ++i) { + qreal timestamp = QDateTime::currentMSecsSinceEpoch() + i * 1000000; + + QCandlestickSet *set = new QCandlestickSet(timestamp); + set->setOpen(4); + set->setHigh(4); + set->setLow(1); + set->setClose(1); + + m_sets.append(set); + } +} + +void tst_QCandlestickSeries::cleanup() +{ + foreach (QCandlestickSet *set, m_sets) { + m_series->remove(set); + m_sets.removeAll(set); + delete set; + } + + delete m_series; + m_series = nullptr; +} + +void tst_QCandlestickSeries::qCandlestickSeries() +{ + QCandlestickSeries *series = new QCandlestickSeries(); + + QVERIFY(series != nullptr); + + delete series; + series = nullptr; +} + +void tst_QCandlestickSeries::append() +{ + QCOMPARE(m_series->count(), 0); + + // Try adding set + QCandlestickSet *set1 = new QCandlestickSet(1234.0); + QVERIFY(m_series->append(set1)); + QCOMPARE(m_series->count(), 1); + + // Try adding another set + QCandlestickSet *set2 = new QCandlestickSet(2345.0); + QVERIFY(m_series->append(set2)); + QCOMPARE(m_series->count(), 2); + + // Try adding same set again + QVERIFY(!m_series->append(set2)); + QCOMPARE(m_series->count(), 2); + + // Try adding null set + QVERIFY(!m_series->append(nullptr)); + QCOMPARE(m_series->count(), 2); +} + +void tst_QCandlestickSeries::remove() +{ + m_series->append(m_sets); + QCOMPARE(m_series->count(), m_sets.count()); + + // Try to remove null pointer (should not remove, should not crash) + QVERIFY(!m_series->remove(nullptr)); + QCOMPARE(m_series->count(), m_sets.count()); + + // Try to remove invalid pointer (should not remove, should not crash) + QVERIFY(!m_series->remove((QCandlestickSet *)(m_sets.at(0) + 1))); + QCOMPARE(m_series->count(), m_sets.count()); + + // Remove some sets + const int removeCount = 3; + for (int i = 0; i < removeCount; ++i) + QVERIFY(m_series->remove(m_sets.at(i))); + QCOMPARE(m_series->count(), m_sets.count() - removeCount); + + for (int i = removeCount; i < m_sets.count(); ++i) + QCOMPARE(m_series->candlestickSets().at(i - removeCount), m_sets.at(i)); + + // Try removing all sets again (should be ok, even if some sets have already been removed) + for (int i = 0; i < m_sets.count(); ++i) + m_series->remove(m_sets.at(i)); + QCOMPARE(m_series->count(), 0); +} + +void tst_QCandlestickSeries::appendList() +{ + QCOMPARE(m_series->count(), 0); + + // Append new sets (should succeed, count should match the count of sets) + QVERIFY(m_series->append(m_sets)); + QCOMPARE(m_series->count(), m_series->count()); + + // Append same sets again (should fail, count should remain same) + QVERIFY(!m_series->append(m_sets)); + QCOMPARE(m_series->count(), m_series->count()); + + // Try append empty list (should succeed, but count should remain same) + QList invalidList; + QVERIFY(m_series->append(invalidList)); + QCOMPARE(m_series->count(), m_sets.count()); + + // Try append list with one new and one existing set (should fail, count remains same) + invalidList.append(new QCandlestickSet()); + invalidList.append(m_sets.at(0)); + QVERIFY(!m_series->append(invalidList)); + QCOMPARE(m_series->count(), m_sets.count()); + delete invalidList.at(0); + invalidList.clear(); + + // Try append list with null pointers (should fail, count remains same) + QVERIFY(invalidList.isEmpty()); + invalidList.append(nullptr); + invalidList.append(nullptr); + invalidList.append(nullptr); + QVERIFY(!m_series->append(invalidList)); + QCOMPARE(m_series->count(), m_sets.count()); +} + +void tst_QCandlestickSeries::removeList() +{ + m_series->append(m_sets); + QCOMPARE(m_series->count(), m_sets.count()); + + // Try remove empty list (should fail, but count should remain same) + QList invalidList; + QVERIFY(!m_series->remove(invalidList)); + QCOMPARE(m_series->count(), m_sets.count()); + + // Try remove list with one new and one existing set (should fail, count remains same) + invalidList.append(new QCandlestickSet()); + invalidList.append(m_sets.at(0)); + QVERIFY(!m_series->remove(invalidList)); + QCOMPARE(m_series->count(), m_sets.count()); + delete invalidList.at(0); + invalidList.clear(); + + // Try remove list with null pointers (should fail, count remains same) + QVERIFY(invalidList.isEmpty()); + invalidList.append(nullptr); + invalidList.append(nullptr); + invalidList.append(nullptr); + QVERIFY(!m_series->remove(invalidList)); + QCOMPARE(m_series->count(), m_sets.count()); + + // Remove all sets (should succeed, count should be zero) + QVERIFY(m_series->remove(m_sets)); + QCOMPARE(m_series->count(), 0); + + // Remove same sets again (should fail, count should remain zero) + QVERIFY(!m_series->remove(m_sets)); + QCOMPARE(m_series->count(), 0); +} + +void tst_QCandlestickSeries::insert() +{ + QCOMPARE(m_series->count(), 0); + + QSignalSpy countSpy(m_series, SIGNAL(countChanged())); + QSignalSpy addedSpy(m_series, SIGNAL(candlestickSetsAdded(QList))); + + for (int i = 0; i < m_sets.count(); ++i) { + QCandlestickSet *set = m_sets.at(i); + QVERIFY(m_series->insert(0, set)); + QCOMPARE(m_series->count(), i + 1); + QTRY_COMPARE(countSpy.count(), i + 1); + QTRY_COMPARE(addedSpy.count(), i + 1); + + QList args = addedSpy.value(i); + QCOMPARE(args.count(), 1); + QList sets = qvariant_cast>(args.at(0)); + QCOMPARE(sets.count(), 1); + QCOMPARE(sets.first(), set); + } +} + +void tst_QCandlestickSeries::take() +{ + m_series->append(m_sets); + QCOMPARE(m_series->count(), m_sets.count()); + + QSignalSpy countSpy(m_series, SIGNAL(countChanged())); + QSignalSpy removedSpy(m_series, SIGNAL(candlestickSetsRemoved(QList))); + + for (int i = 0; i < m_sets.count(); ++i) { + QCandlestickSet *set = m_sets.at(i); + QVERIFY(m_series->take(set)); + QCOMPARE(m_series->count(), m_sets.count() - i - 1); + QTRY_COMPARE(countSpy.count(), i + 1); + QTRY_COMPARE(removedSpy.count(), i + 1); + + QList args = removedSpy.value(i); + QCOMPARE(args.count(), 1); + QList sets = qvariant_cast>(args.at(0)); + QCOMPARE(sets.count(), 1); + QCOMPARE(sets.first(), set); + } +} + +void tst_QCandlestickSeries::clear() +{ + m_series->append(m_sets); + QCOMPARE(m_series->count(), m_sets.count()); + + m_series->clear(); + QCOMPARE(m_series->count(), 0); +} + +void tst_QCandlestickSeries::candlestickSets() +{ + m_series->append(m_sets); + QCOMPARE(m_series->candlestickSets(), m_sets); + + for (int i = 0; i < m_sets.count(); ++i) + QCOMPARE(m_series->candlestickSets().at(i), m_sets.at(i)); + + m_series->clear(); + QCOMPARE(m_series->candlestickSets(), QList()); +} + +void tst_QCandlestickSeries::count() +{ + m_series->append(m_sets); + QCOMPARE(m_series->count(), m_sets.count()); + QCOMPARE(m_series->count(), m_series->candlestickSets().count()); +} + +void tst_QCandlestickSeries::type() +{ + QCOMPARE(m_series->type(), QAbstractSeries::SeriesTypeCandlestick); +} + +void tst_QCandlestickSeries::maximumColumnWidth_data() +{ + QTest::addColumn("maximumColumnWidth"); + QTest::addColumn("expectedMaximumColumnWidth"); + + QTest::newRow("maximum column width less than -1.0") << -3.0 << -1.0; + QTest::newRow("maximum column equals to -1.0") << -1.0 << -1.0; + QTest::newRow("maximum column width greater than -1.0, but less than zero") << -0.5 << -1.0; + QTest::newRow("maximum column width equals zero") << 0.0 << 0.0; + QTest::newRow("maximum column width greater than zero") << 1.0 << 1.0; + QTest::newRow("maximum column width contains a fractional part") << 3.4 << 3.4; +} + +void tst_QCandlestickSeries::maximumColumnWidth() +{ + QFETCH(qreal, maximumColumnWidth); + QFETCH(qreal, expectedMaximumColumnWidth); + + QSignalSpy spy(m_series, SIGNAL(maximumColumnWidthChanged())); + + m_series->setMaximumColumnWidth(maximumColumnWidth); + QCOMPARE(m_series->maximumColumnWidth(), expectedMaximumColumnWidth); + QCOMPARE(spy.count(), 1); + + // Try set same maximum column width + m_series->setMaximumColumnWidth(expectedMaximumColumnWidth); + QCOMPARE(m_series->maximumColumnWidth(), expectedMaximumColumnWidth); + QCOMPARE(spy.count(), 1); +} + +void tst_QCandlestickSeries::minimumColumnWidth_data() +{ + QTest::addColumn("minimumColumnWidth"); + QTest::addColumn("expectedMinimumColumnWidth"); + + QTest::newRow("minimum column width less than -1.0") << -3.0 << -1.0; + QTest::newRow("minimum column equals to -1.0") << -1.0 << -1.0; + QTest::newRow("minimum column width greater than -1.0, but less than zero") << -0.5 << -1.0; + QTest::newRow("minimum column width equals zero") << 0.0 << 0.0; + QTest::newRow("minimum column width greater than zero") << 1.0 << 1.0; + QTest::newRow("minimum column width contains a fractional part") << 3.4 << 3.4; +} + +void tst_QCandlestickSeries::minimumColumnWidth() +{ + QFETCH(qreal, minimumColumnWidth); + QFETCH(qreal, expectedMinimumColumnWidth); + + QSignalSpy spy(m_series, SIGNAL(minimumColumnWidthChanged())); + + m_series->setMinimumColumnWidth(minimumColumnWidth); + QCOMPARE(m_series->minimumColumnWidth(), expectedMinimumColumnWidth); + QCOMPARE(spy.count(), 1); + + // Try set same minimum column width + m_series->setMinimumColumnWidth(expectedMinimumColumnWidth); + QCOMPARE(m_series->minimumColumnWidth(), expectedMinimumColumnWidth); + QCOMPARE(spy.count(), 1); +} + +void tst_QCandlestickSeries::bodyWidth_data() +{ + QTest::addColumn("bodyWidth"); + QTest::addColumn("expectedBodyWidth"); + + QTest::newRow("body width less than zero") << -1.0 << 0.0; + QTest::newRow("body width equals zero") << 0.0 << 0.0; + QTest::newRow("body width greater than zero and less than one") << 0.5 << 0.5; + QTest::newRow("body width equals one") << 1.0 << 1.0; + QTest::newRow("body width greater than one") << 2.0 << 1.0; +} + +void tst_QCandlestickSeries::bodyWidth() +{ + QFETCH(qreal, bodyWidth); + QFETCH(qreal, expectedBodyWidth); + + QSignalSpy spy(m_series, SIGNAL(bodyWidthChanged())); + + m_series->setBodyWidth(bodyWidth); + QCOMPARE(m_series->bodyWidth(), expectedBodyWidth); + QCOMPARE(spy.count(), 1); + + // Try set same body width + m_series->setBodyWidth(bodyWidth); + QCOMPARE(m_series->bodyWidth(), expectedBodyWidth); + QCOMPARE(spy.count(), 1); +} + +void tst_QCandlestickSeries::bodyOutlineVisible() +{ + QSignalSpy spy(m_series, SIGNAL(bodyOutlineVisibilityChanged())); + + bool visible = !m_series->bodyOutlineVisible(); + m_series->setBodyOutlineVisible(visible); + QCOMPARE(m_series->bodyOutlineVisible(), visible); + QCOMPARE(spy.count(), 1); + + // Try set same body outline visibility + m_series->setBodyOutlineVisible(visible); + QCOMPARE(m_series->bodyOutlineVisible(), visible); + QCOMPARE(spy.count(), 1); +} + +void tst_QCandlestickSeries::capsWidth_data() +{ + QTest::addColumn("capsWidth"); + QTest::addColumn("expectedCapsWidth"); + + QTest::newRow("caps width less than zero") << -1.0 << 0.0; + QTest::newRow("caps width equals zero") << 0.0 << 0.0; + QTest::newRow("caps width greater than zero and less than one") << 0.5 << 0.5; + QTest::newRow("caps width equals one") << 1.0 << 1.0; + QTest::newRow("caps width greater than one") << 2.0 << 1.0; +} + +void tst_QCandlestickSeries::capsWidth() +{ + QFETCH(qreal, capsWidth); + QFETCH(qreal, expectedCapsWidth); + + QSignalSpy spy(m_series, SIGNAL(capsWidthChanged())); + + m_series->setCapsWidth(capsWidth); + QCOMPARE(m_series->capsWidth(), expectedCapsWidth); + QCOMPARE(spy.count(), 1); + + // Try set same caps width + m_series->setCapsWidth(capsWidth); + QCOMPARE(m_series->capsWidth(), expectedCapsWidth); + QCOMPARE(spy.count(), 1); +} + +void tst_QCandlestickSeries::capsVisible() +{ + QSignalSpy spy(m_series, SIGNAL(capsVisibilityChanged())); + + bool visible = !m_series->capsVisible(); + m_series->setCapsVisible(visible); + QCOMPARE(m_series->capsVisible(), visible); + QCOMPARE(spy.count(), 1); + + // Try set same caps visibility + m_series->setCapsVisible(visible); + QCOMPARE(m_series->capsVisible(), visible); + QCOMPARE(spy.count(), 1); +} + +void tst_QCandlestickSeries::increasingColor() +{ + QSignalSpy spy(m_series, SIGNAL(increasingColorChanged())); + + // Try set new increasing color + QColor newColor(200, 200, 200, 200); + m_series->setIncreasingColor(newColor); + QCOMPARE(m_series->increasingColor(), newColor); + QCOMPARE(spy.count(), 1); + + // Try set same increasing color again + m_series->setIncreasingColor(newColor); + QCOMPARE(m_series->increasingColor(), newColor); + QCOMPARE(spy.count(), 1); + + // Try set invalid increasing color (should change to default color) + QColor defaultColor = m_series->brush().color(); + defaultColor.setAlpha(128); + m_series->setIncreasingColor(QColor()); + QCOMPARE(m_series->increasingColor(), defaultColor); + QCOMPARE(spy.count(), 2); + + // Set new brush, increasing color should change accordingly + QBrush brush(newColor); + defaultColor = brush.color(); + defaultColor.setAlpha(128); + m_series->setBrush(brush); + QCOMPARE(m_series->increasingColor(), defaultColor); + QCOMPARE(spy.count(), 3); +} + +void tst_QCandlestickSeries::decreasingColor() +{ + QSignalSpy spy(m_series, SIGNAL(decreasingColorChanged())); + + // Try set new decreasing color + QColor newColor(200, 200, 200, 200); + m_series->setDecreasingColor(newColor); + QCOMPARE(m_series->decreasingColor(), newColor); + QCOMPARE(spy.count(), 1); + + // Try set same decreasing color again + m_series->setDecreasingColor(newColor); + QCOMPARE(m_series->decreasingColor(), newColor); + QCOMPARE(spy.count(), 1); + + // Try set invalid decreasing color (should change to default color) + m_series->setDecreasingColor(QColor()); + QCOMPARE(m_series->decreasingColor(), m_series->brush().color()); + QCOMPARE(spy.count(), 2); + + // Set new brush, decreasing color should change accordingly + m_series->setBrush(QBrush(newColor)); + QCOMPARE(m_series->decreasingColor(), m_series->brush().color()); + QCOMPARE(spy.count(), 3); +} + +void tst_QCandlestickSeries::brush() +{ + QSignalSpy spy(m_series, SIGNAL(brushChanged())); + + QBrush brush(QColor(128, 128, 128, 128)); + QColor increasingColor(brush.color()); + increasingColor.setAlpha(128); + QColor decreasingColor(brush.color()); + m_series->setBrush(brush); + QCOMPARE(m_series->brush(), brush); + QCOMPARE(m_series->increasingColor(), increasingColor); + QCOMPARE(m_series->decreasingColor(), decreasingColor); + QCOMPARE(spy.count(), 1); + + // Try set same brush + m_series->setBrush(brush); + QCOMPARE(m_series->brush(), brush); + QCOMPARE(m_series->increasingColor(), increasingColor); + QCOMPARE(m_series->decreasingColor(), decreasingColor); + QCOMPARE(spy.count(), 1); +} + +void tst_QCandlestickSeries::pen() +{ + QSignalSpy spy(m_series, SIGNAL(penChanged())); + + QPen pen(QColor(128, 128, 128, 128)); + m_series->setPen(pen); + QCOMPARE(m_series->pen(), pen); + QCOMPARE(spy.count(), 1); + + // Try set same pen + m_series->setPen(pen); + QCOMPARE(m_series->pen(), pen); + QCOMPARE(spy.count(), 1); +} + +void tst_QCandlestickSeries::mouseClicked() +{ + SKIP_IF_CANNOT_TEST_MOUSE_EVENTS(); + + QVERIFY(m_series->append(m_sets)); + QCOMPARE(m_series->count(), m_sets.count()); + + QCandlestickSet *set1 = m_series->candlestickSets().at(1); + QCandlestickSet *set2 = m_series->candlestickSets().at(2); + + QSignalSpy seriesSpy(m_series, SIGNAL(clicked(QCandlestickSet *))); + QSignalSpy setSpy1(set1, SIGNAL(clicked())); + QSignalSpy setSpy2(set2, SIGNAL(clicked())); + + QChartView view(new QChart()); + view.resize(400, 300); + view.chart()->addSeries(m_series); + view.chart()->createDefaultAxes(); + view.show(); + QTest::qWaitForWindowShown(&view); + + // Calculate expected layout for candlesticks + QRectF plotArea = view.chart()->plotArea(); + qreal candlestickWidth = plotArea.width() / m_series->count(); + qreal candlestickHeight = plotArea.height(); + + QMap layout; + layout.insert(set1, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set1), + plotArea.top(), candlestickWidth, candlestickHeight)); + layout.insert(set2, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set2), + plotArea.top(), candlestickWidth, candlestickHeight)); + + // Click set 1 + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, layout.value(set1).center().toPoint()); + QCoreApplication::processEvents(QEventLoop::AllEvents, 1000); + + QCOMPARE(seriesSpy.count(), 1); + QCOMPARE(setSpy1.count(), 1); + QCOMPARE(setSpy2.count(), 0); + + QList seriesSpyArgs = seriesSpy.takeFirst(); + QCOMPARE(seriesSpyArgs.count(), 1); + QCOMPARE(qvariant_cast(seriesSpyArgs.at(0)), set1); + seriesSpyArgs.clear(); + + QVERIFY(setSpy1.takeFirst().isEmpty()); + + // Click set 2 + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, layout.value(set2).center().toPoint()); + QCoreApplication::processEvents(QEventLoop::AllEvents, 1000); + + QCOMPARE(seriesSpy.count(), 1); + QCOMPARE(setSpy1.count(), 0); + QCOMPARE(setSpy2.count(), 1); + + seriesSpyArgs = seriesSpy.takeFirst(); + QCOMPARE(seriesSpyArgs.count(), 1); + QCOMPARE(qvariant_cast(seriesSpyArgs.at(0)), set2); + seriesSpyArgs.clear(); + + QVERIFY(setSpy2.takeFirst().isEmpty()); +} + +void tst_QCandlestickSeries::mouseHovered() +{ + SKIP_IF_CANNOT_TEST_MOUSE_EVENTS(); + + QVERIFY(m_series->append(m_sets)); + QCOMPARE(m_series->count(), m_sets.count()); + + QCandlestickSet *set1 = m_series->candlestickSets().at(1); + QCandlestickSet *set2 = m_series->candlestickSets().at(2); + + QSignalSpy seriesSpy(m_series, SIGNAL(hovered(bool, QCandlestickSet *))); + QSignalSpy setSpy1(set1, SIGNAL(hovered(bool))); + QSignalSpy setSpy2(set2, SIGNAL(hovered(bool))); + + QChartView view(new QChart()); + view.resize(400, 300); + view.chart()->addSeries(m_series); + view.chart()->createDefaultAxes(); + view.show(); + QTest::qWaitForWindowShown(&view); + + // This is hack since view does not get events otherwise + view.setMouseTracking(true); + + // Calculate expected layout for candlesticks + QRectF plotArea = view.chart()->plotArea(); + qreal candlestickWidth = plotArea.width() / m_series->count(); + qreal candlestickHeight = plotArea.height(); + + QMap layout; + layout.insert(set1, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set1), + plotArea.top(), candlestickWidth, candlestickHeight)); + layout.insert(set2, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set2), + plotArea.top(), candlestickWidth, candlestickHeight)); + + // Move mouse to left border + QTest::mouseMove(view.viewport(), QPoint(0, layout.value(set1).center().y())); + QCoreApplication::processEvents(QEventLoop::AllEvents, 1000); + + QCOMPARE(seriesSpy.count(), 0); + QCOMPARE(setSpy1.count(), 0); + QCOMPARE(setSpy2.count(), 0); + + // Move mouse on top of set 1 + QTest::mouseMove(view.viewport(), layout.value(set1).center().toPoint()); + QCoreApplication::processEvents(QEventLoop::AllEvents, 1000); + + QCOMPARE(seriesSpy.count(), 1); + QCOMPARE(setSpy1.count(), 1); + QCOMPARE(setSpy2.count(), 0); + + QList seriesSpyArgs = seriesSpy.takeFirst(); + QCOMPARE(qvariant_cast(seriesSpyArgs.at(1)), set1); + QCOMPARE(seriesSpyArgs.at(0).type(), QVariant::Bool); + QCOMPARE(seriesSpyArgs.at(0).toBool(), true); + seriesSpyArgs.clear(); + + QList setSpyArgs = setSpy1.takeFirst(); + QCOMPARE(setSpyArgs.at(0).type(), QVariant::Bool); + QCOMPARE(setSpyArgs.at(0).toBool(), true); + setSpyArgs.clear(); + + // Move mouse from top of set 1 to top of set 2 + QTest::mouseMove(view.viewport(), layout.value(set2).center().toPoint()); + QCoreApplication::processEvents(QEventLoop::AllEvents, 1000); + + QCOMPARE(seriesSpy.count(), 2); + QCOMPARE(setSpy1.count(), 1); + QCOMPARE(setSpy2.count(), 1); + + // Should leave set 1 + seriesSpyArgs = seriesSpy.takeFirst(); + QCOMPARE(qvariant_cast(seriesSpyArgs.at(1)), set1); + QCOMPARE(seriesSpyArgs.at(0).type(), QVariant::Bool); + QCOMPARE(seriesSpyArgs.at(0).toBool(), false); + // Don't call seriesSpyArgs.clear() here + + setSpyArgs = setSpy1.takeFirst(); + QCOMPARE(setSpyArgs.at(0).type(), QVariant::Bool); + QCOMPARE(setSpyArgs.at(0).toBool(), false); + // Don't call setSpyArgs.clear() here + + // Should enter set 2 + seriesSpyArgs = seriesSpy.takeFirst(); + QCOMPARE(qvariant_cast(seriesSpyArgs.at(1)), set2); + QCOMPARE(seriesSpyArgs.at(0).type(), QVariant::Bool); + QCOMPARE(seriesSpyArgs.at(0).toBool(), true); + seriesSpyArgs.clear(); + + setSpyArgs = setSpy2.takeFirst(); + QCOMPARE(setSpyArgs.at(0).type(), QVariant::Bool); + QCOMPARE(setSpyArgs.at(0).toBool(), true); + setSpyArgs.clear(); + + // Move mouse from top of set 2 to background + QTest::mouseMove(view.viewport(), QPoint(layout.value(set2).center().x(), 0)); + QCoreApplication::processEvents(QEventLoop::AllEvents, 1000); + + QCOMPARE(seriesSpy.count(), 1); + QCOMPARE(setSpy1.count(), 0); + QCOMPARE(setSpy2.count(), 1); + + // Should leave set 2 + seriesSpyArgs = seriesSpy.takeFirst(); + QCOMPARE(qvariant_cast(seriesSpyArgs.at(1)), set2); + QCOMPARE(seriesSpyArgs.at(0).type(), QVariant::Bool); + QCOMPARE(seriesSpyArgs.at(0).toBool(), false); + seriesSpyArgs.clear(); + + setSpyArgs = setSpy2.takeFirst(); + QCOMPARE(setSpyArgs.at(0).type(), QVariant::Bool); + QCOMPARE(setSpyArgs.at(0).toBool(), false); + setSpyArgs.clear(); +} + +void tst_QCandlestickSeries::mousePressed() +{ + SKIP_IF_CANNOT_TEST_MOUSE_EVENTS(); + + QVERIFY(m_series->append(m_sets)); + QCOMPARE(m_series->count(), m_sets.count()); + + QCandlestickSet *set1 = m_series->candlestickSets().at(1); + QCandlestickSet *set2 = m_series->candlestickSets().at(2); + + QSignalSpy seriesSpy(m_series, SIGNAL(pressed(QCandlestickSet *))); + QSignalSpy setSpy1(set1, SIGNAL(pressed())); + QSignalSpy setSpy2(set2, SIGNAL(pressed())); + + QChartView view(new QChart()); + view.resize(400, 300); + view.chart()->addSeries(m_series); + view.chart()->createDefaultAxes(); + view.show(); + QTest::qWaitForWindowShown(&view); + + // Calculate expected layout for candlesticks + QRectF plotArea = view.chart()->plotArea(); + qreal candlestickWidth = plotArea.width() / m_series->count(); + qreal candlestickHeight = plotArea.height(); + + QMap layout; + layout.insert(set1, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set1), + plotArea.top(), candlestickWidth, candlestickHeight)); + layout.insert(set2, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set2), + plotArea.top(), candlestickWidth, candlestickHeight)); + + // Press set 1 + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, layout.value(set1).center().toPoint()); + QCoreApplication::processEvents(QEventLoop::AllEvents, 1000); + + QCOMPARE(seriesSpy.count(), 1); + QCOMPARE(setSpy1.count(), 1); + QCOMPARE(setSpy2.count(), 0); + + QList seriesSpyArgs = seriesSpy.takeFirst(); + QCOMPARE(seriesSpyArgs.count(), 1); + QCOMPARE(qvariant_cast(seriesSpyArgs.at(0)), set1); + seriesSpyArgs.clear(); + + QVERIFY(setSpy1.takeFirst().isEmpty()); + + // Press set 2 + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, layout.value(set2).center().toPoint()); + QCoreApplication::processEvents(QEventLoop::AllEvents, 1000); + + QCOMPARE(seriesSpy.count(), 1); + QCOMPARE(setSpy1.count(), 0); + QCOMPARE(setSpy2.count(), 1); + + seriesSpyArgs = seriesSpy.takeFirst(); + QCOMPARE(seriesSpyArgs.count(), 1); + QCOMPARE(qvariant_cast(seriesSpyArgs.at(0)), set2); + seriesSpyArgs.clear(); + + QVERIFY(setSpy2.takeFirst().isEmpty()); +} + +void tst_QCandlestickSeries::mouseReleased() +{ + SKIP_IF_CANNOT_TEST_MOUSE_EVENTS(); + + QVERIFY(m_series->append(m_sets)); + QCOMPARE(m_series->count(), m_sets.count()); + + QCandlestickSet *set1 = m_series->candlestickSets().at(1); + QCandlestickSet *set2 = m_series->candlestickSets().at(2); + + QSignalSpy seriesSpy(m_series, SIGNAL(released(QCandlestickSet *))); + QSignalSpy setSpy1(set1, SIGNAL(released())); + QSignalSpy setSpy2(set2, SIGNAL(released())); + + QChartView view(new QChart()); + view.resize(400, 300); + view.chart()->addSeries(m_series); + view.chart()->createDefaultAxes(); + view.show(); + QTest::qWaitForWindowShown(&view); + + // Calculate expected layout for candlesticks + QRectF plotArea = view.chart()->plotArea(); + qreal candlestickWidth = plotArea.width() / m_series->count(); + qreal candlestickHeight = plotArea.height(); + + QMap layout; + layout.insert(set1, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set1), + plotArea.top(), candlestickWidth, candlestickHeight)); + layout.insert(set2, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set2), + plotArea.top(), candlestickWidth, candlestickHeight)); + + // Release mouse over set 1 + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, layout.value(set1).center().toPoint()); + QCoreApplication::processEvents(QEventLoop::AllEvents, 1000); + + QCOMPARE(seriesSpy.count(), 1); + QCOMPARE(setSpy1.count(), 1); + QCOMPARE(setSpy2.count(), 0); + + QList seriesSpyArgs = seriesSpy.takeFirst(); + QCOMPARE(seriesSpyArgs.count(), 1); + QCOMPARE(qvariant_cast(seriesSpyArgs.at(0)), set1); + seriesSpyArgs.clear(); + + QVERIFY(setSpy1.takeFirst().isEmpty()); + + // Release mouse over set 2 + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, layout.value(set2).center().toPoint()); + QCoreApplication::processEvents(QEventLoop::AllEvents, 1000); + + QCOMPARE(seriesSpy.count(), 1); + QCOMPARE(setSpy1.count(), 0); + QCOMPARE(setSpy2.count(), 1); + + seriesSpyArgs = seriesSpy.takeFirst(); + QCOMPARE(seriesSpyArgs.count(), 1); + QCOMPARE(qvariant_cast(seriesSpyArgs.at(0)), set2); + seriesSpyArgs.clear(); + + QVERIFY(setSpy2.takeFirst().isEmpty()); +} + +void tst_QCandlestickSeries::mouseDoubleClicked() +{ + SKIP_IF_CANNOT_TEST_MOUSE_EVENTS(); + + QVERIFY(m_series->append(m_sets)); + QCOMPARE(m_series->count(), m_sets.count()); + + QCandlestickSet *set1 = m_series->candlestickSets().at(1); + QCandlestickSet *set2 = m_series->candlestickSets().at(2); + + QSignalSpy seriesSpy(m_series, SIGNAL(doubleClicked(QCandlestickSet *))); + QSignalSpy setSpy1(set1, SIGNAL(doubleClicked())); + QSignalSpy setSpy2(set2, SIGNAL(doubleClicked())); + + QChartView view(new QChart()); + view.resize(400, 300); + view.chart()->addSeries(m_series); + view.chart()->createDefaultAxes(); + view.show(); + QTest::qWaitForWindowShown(&view); + + // Calculate expected layout for candlesticks + QRectF plotArea = view.chart()->plotArea(); + qreal candlestickWidth = plotArea.width() / m_series->count(); + qreal candlestickHeight = plotArea.height(); + + QMap layout; + layout.insert(set1, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set1), + plotArea.top(), candlestickWidth, candlestickHeight)); + layout.insert(set2, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set2), + plotArea.top(), candlestickWidth, candlestickHeight)); + + // Double-click set 1 + QTest::mouseDClick(view.viewport(), Qt::LeftButton, 0, layout.value(set1).center().toPoint()); + QCoreApplication::processEvents(QEventLoop::AllEvents, 1000); + + QCOMPARE(seriesSpy.count(), 1); + QCOMPARE(setSpy1.count(), 1); + QCOMPARE(setSpy2.count(), 0); + + QList seriesSpyArgs = seriesSpy.takeFirst(); + QCOMPARE(seriesSpyArgs.count(), 1); + QCOMPARE(qvariant_cast(seriesSpyArgs.at(0)), set1); + seriesSpyArgs.clear(); + + QVERIFY(setSpy1.takeFirst().isEmpty()); +} + +QTEST_MAIN(tst_QCandlestickSeries) + +#include "tst_qcandlestickseries.moc" diff --git a/tests/auto/qcandlestickset/qcandlestickset.pro b/tests/auto/qcandlestickset/qcandlestickset.pro new file mode 100644 index 0000000..e67d1df --- /dev/null +++ b/tests/auto/qcandlestickset/qcandlestickset.pro @@ -0,0 +1,5 @@ +!include( ../auto.pri ) { + error( "Couldn't find the auto.pri file!" ) +} + +SOURCES += tst_qcandlestickset.cpp diff --git a/tests/auto/qcandlestickset/tst_qcandlestickset.cpp b/tests/auto/qcandlestickset/tst_qcandlestickset.cpp new file mode 100644 index 0000000..5e94d4c --- /dev/null +++ b/tests/auto/qcandlestickset/tst_qcandlestickset.cpp @@ -0,0 +1,284 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +QT_CHARTS_USE_NAMESPACE + +class tst_QCandlestickSet : public QObject +{ + Q_OBJECT + +public Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + +private Q_SLOTS: + void qCandlestickSet_data(); + void qCandlestickSet(); + void timestamp_data(); + void timestamp(); + void open_data(); + void open(); + void high_data(); + void high(); + void low_data(); + void low(); + void close_data(); + void close(); + void brush(); + void pen(); + +private: + QCandlestickSet* m_candlestickSet; +}; + +void tst_QCandlestickSet::initTestCase() +{ +} + +void tst_QCandlestickSet::cleanupTestCase() +{ + QTest::qWait(1); // Allow final deleteLaters to run +} + +void tst_QCandlestickSet::init() +{ + m_candlestickSet = new QCandlestickSet(QDateTime::currentMSecsSinceEpoch()); + m_candlestickSet->setOpen(2345.67); + m_candlestickSet->setHigh(4567.89); + m_candlestickSet->setLow(1234.56); + m_candlestickSet->setClose(3456.78); +} + +void tst_QCandlestickSet::cleanup() +{ + delete m_candlestickSet; + m_candlestickSet = nullptr; +} + +void tst_QCandlestickSet::qCandlestickSet_data() +{ + QTest::addColumn("timestamp"); + QTest::addColumn("expectedTimestamp"); + + QTest::newRow("timestamp less than zero") << -1.0 << 0.0; + QTest::newRow("timestamp equals zero") << 0.0 << 0.0; + QTest::newRow("timestamp greater than zero") << 1.0 << 1.0; + QTest::newRow("timestamp rounded down") << 4.321 << 4.0; + QTest::newRow("timestamp rounded up") << 5.678 << 6.0; +} + +void tst_QCandlestickSet::qCandlestickSet() +{ + QFETCH(qreal, timestamp); + QFETCH(qreal, expectedTimestamp); + + QCandlestickSet candlestickSet(timestamp); + QCOMPARE(candlestickSet.timestamp(), expectedTimestamp); +} + +void tst_QCandlestickSet::timestamp_data() +{ + QTest::addColumn("timestamp"); + QTest::addColumn("expectedTimestamp"); + + QTest::newRow("timestamp less than zero") << -1.0 << 0.0; + QTest::newRow("timestamp equals zero") << 0.0 << 0.0; + QTest::newRow("timestamp greater than zero") << 1.0 << 1.0; + QTest::newRow("timestamp rounded down") << 4.321 << 4.0; + QTest::newRow("timestamp rounded up") << 5.678 << 6.0; +} + +void tst_QCandlestickSet::timestamp() +{ + QFETCH(qreal, timestamp); + QFETCH(qreal, expectedTimestamp); + + QSignalSpy spy(m_candlestickSet, SIGNAL(timestampChanged())); + + m_candlestickSet->setTimestamp(timestamp); + QCOMPARE(m_candlestickSet->timestamp(), expectedTimestamp); + QCOMPARE(spy.count(), 1); + + // Try set same timestamp value + m_candlestickSet->setTimestamp(expectedTimestamp); + QCOMPARE(m_candlestickSet->timestamp(), expectedTimestamp); + QCOMPARE(spy.count(), 1); +} + +void tst_QCandlestickSet::open_data() +{ + QTest::addColumn("open"); + + QTest::newRow("open less than zero") << -1.234; + QTest::newRow("open equals zero") << 0.0; + QTest::newRow("open greater than zero") << 1.234; +} + +void tst_QCandlestickSet::open() +{ + QFETCH(qreal, open); + + QSignalSpy spy(m_candlestickSet, SIGNAL(openChanged())); + + m_candlestickSet->setOpen(open); + QCOMPARE(m_candlestickSet->open(), open); + QCOMPARE(spy.count(), 1); + + // Try set same open value + m_candlestickSet->setOpen(open); + QCOMPARE(m_candlestickSet->open(), open); + QCOMPARE(spy.count(), 1); +} + +void tst_QCandlestickSet::high_data() +{ + QTest::addColumn("high"); + + QTest::newRow("high less than zero") << -1.234; + QTest::newRow("high equals zero") << 0.0; + QTest::newRow("high greater than zero") << 1.234; +} + +void tst_QCandlestickSet::high() +{ + QFETCH(qreal, high); + + QSignalSpy spy(m_candlestickSet, SIGNAL(highChanged())); + + m_candlestickSet->setHigh(high); + QCOMPARE(m_candlestickSet->high(), high); + QCOMPARE(spy.count(), 1); + + // Try set same high value + m_candlestickSet->setHigh(high); + QCOMPARE(m_candlestickSet->high(), high); + QCOMPARE(spy.count(), 1); +} + +void tst_QCandlestickSet::low_data() +{ + QTest::addColumn("low"); + + QTest::newRow("low less than zero") << -1.234; + QTest::newRow("low equals zero") << 0.0; + QTest::newRow("low greater than zero") << 1.234; +} + +void tst_QCandlestickSet::low() +{ + QFETCH(qreal, low); + + QSignalSpy spy(m_candlestickSet, SIGNAL(lowChanged())); + + m_candlestickSet->setLow(low); + QCOMPARE(m_candlestickSet->low(), low); + QCOMPARE(spy.count(), 1); + + // Try set same low value + m_candlestickSet->setLow(low); + QCOMPARE(m_candlestickSet->low(), low); + QCOMPARE(spy.count(), 1); +} + +void tst_QCandlestickSet::close_data() +{ + QTest::addColumn("close"); + + QTest::newRow("close less than zero") << -1.234; + QTest::newRow("close equals zero") << 0.0; + QTest::newRow("close greater than zero") << 1.234; +} + +void tst_QCandlestickSet::close() +{ + QFETCH(qreal, close); + + QSignalSpy spy(m_candlestickSet, SIGNAL(closeChanged())); + + m_candlestickSet->setClose(close); + QCOMPARE(m_candlestickSet->close(), close); + QCOMPARE(spy.count(), 1); + + // Try set same close value + m_candlestickSet->setClose(close); + QCOMPARE(m_candlestickSet->close(), close); + QCOMPARE(spy.count(), 1); +} + +void tst_QCandlestickSet::brush() +{ + QSignalSpy spy(m_candlestickSet, SIGNAL(brushChanged())); + + QCOMPARE(m_candlestickSet->brush(), QBrush(Qt::NoBrush)); + + m_candlestickSet->setBrush(QBrush(Qt::NoBrush)); + QCOMPARE(m_candlestickSet->brush(), QBrush(Qt::NoBrush)); + QCOMPARE(spy.count(), 0); + + QBrush brush(QColor(128, 128, 128, 128)); + m_candlestickSet->setBrush(brush); + QCOMPARE(m_candlestickSet->brush(), brush); + QCOMPARE(spy.count(), 1); + + // Try set same brush + m_candlestickSet->setBrush(brush); + QCOMPARE(m_candlestickSet->brush(), brush); + QCOMPARE(spy.count(), 1); +} + +void tst_QCandlestickSet::pen() +{ + QSignalSpy spy(m_candlestickSet, SIGNAL(penChanged())); + + QCOMPARE(m_candlestickSet->pen(), QPen(Qt::NoPen)); + + m_candlestickSet->setPen(QPen(Qt::NoPen)); + QCOMPARE(m_candlestickSet->pen(), QPen(Qt::NoPen)); + QCOMPARE(spy.count(), 0); + + QPen pen(QColor(128, 128, 128, 128)); + m_candlestickSet->setPen(pen); + QCOMPARE(m_candlestickSet->pen(), pen); + QCOMPARE(spy.count(), 1); + + // Try set same pen + m_candlestickSet->setPen(pen); + QCOMPARE(m_candlestickSet->pen(), pen); + QCOMPARE(spy.count(), 1); +} + +QTEST_GUILESS_MAIN(tst_QCandlestickSet) + +#include "tst_qcandlestickset.moc" diff --git a/tests/manual/candlesticktester/candlesticktester.pro b/tests/manual/candlesticktester/candlesticktester.pro new file mode 100644 index 0000000..fd927b6 --- /dev/null +++ b/tests/manual/candlesticktester/candlesticktester.pro @@ -0,0 +1,13 @@ +!include( ../../tests.pri ) { + error( "Couldn't find the test.pri file!" ) +} + +QT += widgets + +SOURCES += main.cpp \ + mainwidget.cpp \ + customtablemodel.cpp + +HEADERS += \ + mainwidget.h \ + customtablemodel.h diff --git a/tests/manual/candlesticktester/customtablemodel.cpp b/tests/manual/candlesticktester/customtablemodel.cpp new file mode 100644 index 0000000..e012242 --- /dev/null +++ b/tests/manual/candlesticktester/customtablemodel.cpp @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include "customtablemodel.h" + +CustomTableModel::CustomTableModel(QObject *parent) + : QAbstractTableModel(parent) +{ + m_categories.append(QStringLiteral("Timestamp")); + m_categories.append(QStringLiteral("Open")); + m_categories.append(QStringLiteral("High")); + m_categories.append(QStringLiteral("Low")); + m_categories.append(QStringLiteral("Close")); +} + +CustomTableModel::~CustomTableModel() +{ + qDeleteAll(m_data); +} + +int CustomTableModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return m_data.count(); +} + +int CustomTableModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return m_categories.count(); +} + +QVariant CustomTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation == Qt::Horizontal) + return m_categories[section]; + else + return QStringLiteral("%1").arg(section); +} + +bool CustomTableModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.isValid() && role == Qt::EditRole) { + m_data[index.row()]->replace(index.column(), value.toDouble()); + emit dataChanged(index, index); + + return true; + } + + return false; +} + +QVariant CustomTableModel::data(const QModelIndex &index, int role) const +{ + switch (role) { + case Qt::DisplayRole: + // fall through + case Qt::EditRole: + return m_data[index.row()]->at(index.column()); + case Qt::BackgroundRole: + foreach (QRect rect, m_mapping) { + if (rect.contains(index.column(), index.row())) + return QColor(m_mapping.key(rect)); + } + // cell is not mapped, return white color + return QColor(Qt::white); + default: + return QVariant(); + } +} + +Qt::ItemFlags CustomTableModel::flags(const QModelIndex &index) const +{ + return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; +} + +bool CustomTableModel::insertRows(int row, int count, const QModelIndex &parent) +{ + beginInsertRows(parent, row, row + count - 1); + m_data.append(new QVector(columnCount())); + endInsertRows(); + + return true; +} + +bool CustomTableModel::removeRows(int row, int count, const QModelIndex &parent) +{ + beginRemoveRows(parent, row, row + count - 1); + for (int i = row + count; i >= row; --i) + m_data.removeAt(i); + endRemoveRows(); + + return true; +} + +void CustomTableModel::addRow(QCandlestickSet *set) +{ + bool changed = insertRows(m_data.count(), 1); + + if (changed) { + QVector *row = m_data.last(); + row->insert(0, set->timestamp()); + row->insert(1, set->open()); + row->insert(2, set->high()); + row->insert(3, set->low()); + row->insert(4, set->close()); + } +} + +void CustomTableModel::clearRows() +{ + bool changed = removeRows(0, m_data.count()); + if (changed) + m_data.clear(); +} + +void CustomTableModel::addMapping(QString color, QRect area) +{ + m_mapping.insertMulti(color, area); +} + +void CustomTableModel::clearMapping() +{ + m_mapping.clear(); +} diff --git a/tests/manual/candlesticktester/customtablemodel.h b/tests/manual/candlesticktester/customtablemodel.h new file mode 100644 index 0000000..4a8965c --- /dev/null +++ b/tests/manual/candlesticktester/customtablemodel.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CUSTOMTABLEMODEL_H +#define CUSTOMTABLEMODEL_H + +#include +#include +#include +#include + +QT_CHARTS_BEGIN_NAMESPACE +class QCandlestickSet; +QT_CHARTS_END_NAMESPACE + +QT_CHARTS_USE_NAMESPACE + +class CustomTableModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit CustomTableModel(QObject *parent = nullptr); + virtual ~CustomTableModel(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + + void addRow(QCandlestickSet *set); + void clearRows(); + + void addMapping(QString color, QRect area); + void clearMapping(); + +private: + QStringList m_categories; + QList *> m_data; + QHash m_mapping; +}; + +#endif // CUSTOMTABLEMODEL_H diff --git a/tests/manual/candlesticktester/main.cpp b/tests/manual/candlesticktester/main.cpp new file mode 100644 index 0000000..3c89331 --- /dev/null +++ b/tests/manual/candlesticktester/main.cpp @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "mainwidget.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + MainWidget w; + w.setWindowTitle(QStringLiteral("Candlestick Chart Tester")); + w.resize(1280, 720); + w.show(); + + return a.exec(); +} diff --git a/tests/manual/candlesticktester/mainwidget.cpp b/tests/manual/candlesticktester/mainwidget.cpp new file mode 100644 index 0000000..7364f02 --- /dev/null +++ b/tests/manual/candlesticktester/mainwidget.cpp @@ -0,0 +1,691 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "customtablemodel.h" +#include "mainwidget.h" + +QT_CHARTS_USE_NAMESPACE + +MainWidget::MainWidget(QWidget *parent) + : QWidget(parent), + m_chart(new QChart()), + m_chartView(new QChartView(m_chart, this)), + m_axisX(nullptr), + m_axisY(nullptr), + m_maximumColumnWidth(-1.0), + m_minimumColumnWidth(5.0), + m_bodyOutlineVisible(true), + m_capsVisible(false), + m_bodyWidth(0.5), + m_capsWidth(0.5), + m_customIncreasingColor(false), + m_customDecreasingColor(false), + m_hModelMapper(new QHCandlestickModelMapper(this)) +{ + qsrand(QDateTime::currentDateTime().toTime_t()); + + m_chartView->setRenderHint(QPainter::Antialiasing, false); + + m_hModelMapper->setModel(new CustomTableModel(this)); + m_hModelMapper->setTimestampColumn(0); + m_hModelMapper->setOpenColumn(1); + m_hModelMapper->setHighColumn(2); + m_hModelMapper->setLowColumn(3); + m_hModelMapper->setCloseColumn(4); + + QGridLayout *mainLayout = new QGridLayout(); + mainLayout->addLayout(createSeriesControlsLayout(), 0, 0); + mainLayout->addLayout(createSetsControlsLayout(), 1, 0); + mainLayout->addLayout(createCandlestickControlsLayout(), 2, 0); + mainLayout->addLayout(createMiscellaneousControlsLayout(), 3, 0); + mainLayout->addWidget(m_chartView, 0, 1, mainLayout->rowCount() + 1, 1); + mainLayout->addLayout(createModelMapperControlsLayout(), 0, 2, mainLayout->rowCount(), 1); + setLayout(mainLayout); + + addSeries(); +} + +MainWidget::~MainWidget() +{ +} + +QGridLayout *MainWidget::createSeriesControlsLayout() +{ + QGridLayout *layout = new QGridLayout(); + int row = 0; + + layout->addWidget(new QLabel(QStringLiteral("Series controls:")), row, 0, Qt::AlignLeft); + + QPushButton *addSeriesButton = new QPushButton(QStringLiteral("Add a series")); + connect(addSeriesButton, SIGNAL(clicked(bool)), this, SLOT(addSeries())); + layout->addWidget(addSeriesButton, row++, 1, Qt::AlignLeft); + + QPushButton *removeSeriesButton = new QPushButton(QStringLiteral("Remove a series")); + connect(removeSeriesButton, SIGNAL(clicked(bool)), this, SLOT(removeSeries())); + layout->addWidget(removeSeriesButton, row++, 1, Qt::AlignLeft); + + QPushButton *removeAllSeriesButton = new QPushButton(QStringLiteral("Remove all series")); + connect(removeAllSeriesButton, SIGNAL(clicked(bool)), this, SLOT(removeAllSeries())); + layout->addWidget(removeAllSeriesButton, row++, 1, Qt::AlignLeft); + + return layout; +} + +QGridLayout *MainWidget::createSetsControlsLayout() +{ + QGridLayout *layout = new QGridLayout(); + int row = 0; + + layout->addWidget(new QLabel(QStringLiteral("Sets controls:")), row, 0, Qt::AlignLeft); + + QPushButton *addSetButton = new QPushButton(QStringLiteral("Add a set")); + connect(addSetButton, SIGNAL(clicked(bool)), this, SLOT(addSet())); + layout->addWidget(addSetButton, row++, 1, Qt::AlignLeft); + + QPushButton *insertSetButton = new QPushButton(QStringLiteral("Insert a set")); + connect(insertSetButton, SIGNAL(clicked(bool)), this, SLOT(insertSet())); + layout->addWidget(insertSetButton, row++, 1, Qt::AlignLeft); + + QPushButton *removeSetButton = new QPushButton(QStringLiteral("Remove a set")); + connect(removeSetButton, SIGNAL(clicked(bool)), this, SLOT(removeSet())); + layout->addWidget(removeSetButton, row++, 1, Qt::AlignLeft); + + QPushButton *removeAllSetsButton = new QPushButton(QStringLiteral("Remove all sets")); + connect(removeAllSetsButton, SIGNAL(clicked(bool)), this, SLOT(removeAllSets())); + layout->addWidget(removeAllSetsButton, row++, 1, Qt::AlignLeft); + + return layout; +} + +QGridLayout *MainWidget::createCandlestickControlsLayout() +{ + QGridLayout *layout = new QGridLayout(); + int row = 0; + + layout->addWidget(new QLabel(QStringLiteral("Maximum column width:")), row, 0, Qt::AlignLeft); + QDoubleSpinBox *maximumColumnWidthSpinBox = new QDoubleSpinBox(); + maximumColumnWidthSpinBox->setRange(-1.0, 1024.0); + maximumColumnWidthSpinBox->setDecimals(0); + maximumColumnWidthSpinBox->setValue(m_maximumColumnWidth); + maximumColumnWidthSpinBox->setSingleStep(1.0); + connect(maximumColumnWidthSpinBox, SIGNAL(valueChanged(double)), + this, SLOT(changeMaximumColumnWidth(double))); + layout->addWidget(maximumColumnWidthSpinBox, row++, 1, Qt::AlignLeft); + + layout->addWidget(new QLabel(QStringLiteral("Minimum column width:")), row, 0, Qt::AlignLeft); + QDoubleSpinBox *minimumColumnWidthSpinBox = new QDoubleSpinBox(); + minimumColumnWidthSpinBox->setRange(-1.0, 1024.0); + minimumColumnWidthSpinBox->setDecimals(0); + minimumColumnWidthSpinBox->setValue(m_minimumColumnWidth); + minimumColumnWidthSpinBox->setSingleStep(1.0); + connect(minimumColumnWidthSpinBox, SIGNAL(valueChanged(double)), + this, SLOT(changeMinimumColumnWidth(double))); + layout->addWidget(minimumColumnWidthSpinBox, row++, 1, Qt::AlignLeft); + + QCheckBox *bodyOutlineVisible = new QCheckBox(QStringLiteral("Body outline visible")); + connect(bodyOutlineVisible, SIGNAL(toggled(bool)), this, SLOT(bodyOutlineVisibleToggled(bool))); + bodyOutlineVisible->setChecked(m_bodyOutlineVisible); + layout->addWidget(bodyOutlineVisible, row++, 0, Qt::AlignLeft); + + QCheckBox *capsVisible = new QCheckBox(QStringLiteral("Caps visible")); + connect(capsVisible, SIGNAL(toggled(bool)), this, SLOT(capsVisibleToggled(bool))); + capsVisible->setChecked(m_capsVisible); + layout->addWidget(capsVisible, row++, 0, Qt::AlignLeft); + + layout->addWidget(new QLabel(QStringLiteral("Candlestick body width:")), row, 0, Qt::AlignLeft); + QDoubleSpinBox *bodyWidthSpinBox = new QDoubleSpinBox(); + bodyWidthSpinBox->setRange(-1.0, 2.0); + bodyWidthSpinBox->setValue(m_bodyWidth); + bodyWidthSpinBox->setSingleStep(0.1); + connect(bodyWidthSpinBox, SIGNAL(valueChanged(double)), this, SLOT(changeBodyWidth(double))); + layout->addWidget(bodyWidthSpinBox, row++, 1, Qt::AlignLeft); + + layout->addWidget(new QLabel(QStringLiteral("Candlestick caps width:")), row, 0, Qt::AlignLeft); + QDoubleSpinBox *capsWidthSpinBox = new QDoubleSpinBox(); + capsWidthSpinBox->setRange(-1.0, 2.0); + capsWidthSpinBox->setValue(m_capsWidth); + capsWidthSpinBox->setSingleStep(0.1); + connect(capsWidthSpinBox, SIGNAL(valueChanged(double)), this, SLOT(changeCapsWidth(double))); + layout->addWidget(capsWidthSpinBox, row++, 1, Qt::AlignLeft); + + QCheckBox *increasingColor = new QCheckBox(QStringLiteral("Custom increasing color (only S1)")); + connect(increasingColor, SIGNAL(toggled(bool)), this, SLOT(customIncreasingColorToggled(bool))); + increasingColor->setChecked(m_customIncreasingColor); + layout->addWidget(increasingColor, row++, 0, 1, 2, Qt::AlignLeft); + + QCheckBox *decreasingColor = new QCheckBox(QStringLiteral("Custom decreasing color (only S1)")); + connect(decreasingColor, SIGNAL(toggled(bool)), this, SLOT(customDecreasingColorToggled(bool))); + decreasingColor->setChecked(m_customDecreasingColor); + layout->addWidget(decreasingColor, row++, 0, 1, 2, Qt::AlignLeft); + + return layout; +} + +QGridLayout *MainWidget::createMiscellaneousControlsLayout() +{ + QGridLayout *layout = new QGridLayout(); + int row = 0; + + layout->addWidget(new QLabel(QStringLiteral("Miscellaneous:")), row, 0, Qt::AlignLeft); + + QCheckBox *antialiasingCheckBox = new QCheckBox(QStringLiteral("Antialiasing")); + connect(antialiasingCheckBox, SIGNAL(toggled(bool)), this, SLOT(antialiasingToggled(bool))); + antialiasingCheckBox->setChecked(false); + layout->addWidget(antialiasingCheckBox, row++, 1, Qt::AlignLeft); + + QCheckBox *animationCheckBox = new QCheckBox(QStringLiteral("Animation")); + connect(animationCheckBox, SIGNAL(toggled(bool)), this, SLOT(animationToggled(bool))); + animationCheckBox->setChecked(false); + layout->addWidget(animationCheckBox, row++, 1, Qt::AlignLeft); + + QCheckBox *legendCheckBox = new QCheckBox(QStringLiteral("Legend")); + connect(legendCheckBox, SIGNAL(toggled(bool)), this, SLOT(legendToggled(bool))); + legendCheckBox->setChecked(true); + layout->addWidget(legendCheckBox, row++, 1, Qt::AlignLeft); + + QCheckBox *titleCheckBox = new QCheckBox(QStringLiteral("Title")); + connect(titleCheckBox, SIGNAL(toggled(bool)), this, SLOT(titleToggled(bool))); + titleCheckBox->setChecked(true); + layout->addWidget(titleCheckBox, row++, 1, Qt::AlignLeft); + + layout->addWidget(new QLabel(QStringLiteral("Chart theme:")), row, 0, Qt::AlignLeft); + QComboBox *chartThemeComboBox = new QComboBox(); + chartThemeComboBox->addItem(QStringLiteral("Light")); + chartThemeComboBox->addItem(QStringLiteral("Blue Cerulean")); + chartThemeComboBox->addItem(QStringLiteral("Dark")); + chartThemeComboBox->addItem(QStringLiteral("Brown Sand")); + chartThemeComboBox->addItem(QStringLiteral("Blue Ncs")); + chartThemeComboBox->addItem(QStringLiteral("High Contrast")); + chartThemeComboBox->addItem(QStringLiteral("Blue Icy")); + chartThemeComboBox->addItem(QStringLiteral("Qt")); + connect(chartThemeComboBox,SIGNAL(currentIndexChanged(int)),this,SLOT(changeChartTheme(int))); + layout->addWidget(chartThemeComboBox, row++, 1, Qt::AlignLeft); + + layout->addWidget(new QLabel(QStringLiteral("Axis X:")), row, 0, Qt::AlignLeft); + QComboBox *axisXComboBox = new QComboBox(); + axisXComboBox->addItem(QStringLiteral("BarCategory")); + axisXComboBox->addItem(QStringLiteral("DateTime")); + axisXComboBox->addItem(QStringLiteral("Value")); + connect(axisXComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(changeAxisX(int))); + layout->addWidget(axisXComboBox, row++, 1, Qt::AlignLeft); + + return layout; +} + +QGridLayout *MainWidget::createModelMapperControlsLayout() +{ + QGridLayout *layout = new QGridLayout(); + int row = 0; + + layout->addWidget(new QLabel(QStringLiteral("First series:")), row, 0, Qt::AlignLeft); + + QPushButton *attachModelMapperButton = new QPushButton(QStringLiteral("Attach model mapper")); + connect(attachModelMapperButton, SIGNAL(clicked(bool)), this, SLOT(attachModelMapper())); + layout->addWidget(attachModelMapperButton, row++, 1, Qt::AlignLeft); + + QPushButton *detachModelMappeButton = new QPushButton(QStringLiteral("Detach model mapper")); + connect(detachModelMappeButton, SIGNAL(clicked(bool)), this, SLOT(detachModelMapper())); + layout->addWidget(detachModelMappeButton, row++, 1, Qt::AlignLeft); + + QTableView *tableView = new QTableView(); + tableView->setMinimumSize(320, 480); + tableView->setMaximumSize(320, 480); + tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); + Q_ASSERT_X(m_hModelMapper->model(), Q_FUNC_INFO, "Model is not initialized"); + tableView->setModel(m_hModelMapper->model()); + layout->addWidget(tableView, row++, 0, 1, 2, Qt::AlignLeft); + + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding), row++, 0); + + return layout; +} + +qreal MainWidget::randomValue(int min, int max) const +{ + return (qrand() / (qreal(RAND_MAX) + 1)) * ((qMax(min, max) - qMin(min, max)) + qMin(min, max)); +} + +QCandlestickSet *MainWidget::randomSet(qreal timestamp) +{ + QCandlestickSet *set = new QCandlestickSet(timestamp); + set->setOpen(randomValue(4, 11)); + set->setHigh(randomValue(12, 15)); + set->setLow(randomValue(0, 3)); + set->setClose(randomValue(4, 11)); + + return set; +} + +void MainWidget::updateAxes() +{ + if (m_chart->axes().isEmpty()) + m_chart->createDefaultAxes(); + + QCandlestickSeries *series; + if (!m_chart->series().isEmpty()) + series = qobject_cast(m_chart->series().at(0)); + else + series = nullptr; + + m_axisX = m_chart->axes(Qt::Horizontal).first(); + if (series && !series->candlestickSets().isEmpty()) { + if (m_axisX->type() == QAbstractAxis::AxisTypeBarCategory) { + QBarCategoryAxis *axisX = qobject_cast(m_axisX); + QStringList categories; + for (int i = 0; i < series->candlestickSets().count(); ++i) + categories.append(QString::number(i)); + axisX->setCategories(categories); + } else { // QAbstractAxis::AxisTypeDateTime || QAbstractAxis::AxisTypeValue + qreal msInMonth = 31.0 * 24.0 * 60.0 * 60.0 * 1000.0; + qreal min = series->candlestickSets().first()->timestamp() - msInMonth; + qreal max = series->candlestickSets().last()->timestamp() + msInMonth; + QDateTime minDateTime = QDateTime::fromMSecsSinceEpoch(min); + QDateTime maxDateTime = QDateTime::fromMSecsSinceEpoch(max); + + if (m_axisX->type() == QAbstractAxis::AxisTypeDateTime) + m_axisX->setRange(minDateTime, maxDateTime); + else + m_axisX->setRange(min, max); + } + } + + m_axisY = m_chart->axes(Qt::Vertical).first(); + m_axisY->setMax(15); + m_axisY->setMin(0); +} + +void MainWidget::addSeries() +{ + if (m_chart->series().count() > 9) { + qDebug() << "Maximum series count is 10"; + return; + } + + QCandlestickSeries *series = new QCandlestickSeries(); + series->setName(QStringLiteral("S%1").arg(m_chart->series().count() + 1)); + series->setMaximumColumnWidth(m_maximumColumnWidth); + series->setMinimumColumnWidth(m_minimumColumnWidth); + series->setBodyOutlineVisible(m_bodyOutlineVisible); + series->setBodyWidth(m_bodyWidth); + series->setCapsVisible(m_capsVisible); + series->setCapsWidth(m_capsWidth); + + if (m_chart->series().isEmpty()) { + if (m_customIncreasingColor) + series->setIncreasingColor(QColor(Qt::green)); + if (m_customDecreasingColor) + series->setDecreasingColor(QColor(Qt::red)); + + for (int month = 1; month <= 12; ++month) { + QDateTime dateTime; + dateTime.setDate(QDate(QDateTime::currentDateTime().date().year(), month, 1)); + dateTime.setTime(QTime(12, 34, 56, 789)); + + QCandlestickSet *set = randomSet(dateTime.toMSecsSinceEpoch()); + series->append(set); + } + } else { + QCandlestickSeries *s = qobject_cast(m_chart->series().at(0)); + for (int i = 0; i < s->candlestickSets().count(); ++i) { + QCandlestickSet *set = randomSet(s->candlestickSets().at(i)->timestamp()); + series->append(set); + } + } + + m_chart->addSeries(series); + + updateAxes(); + if (!series->attachedAxes().contains(m_axisX)) + series->attachAxis(m_axisX); + if (!series->attachedAxes().contains(m_axisY)) + series->attachAxis(m_axisY); +} + +void MainWidget::removeSeries() +{ + if (m_chart->series().isEmpty()) { + qDebug() << "Create a series first"; + return; + } + + if (m_chart->series().count() == 1) + detachModelMapper(); + + QCandlestickSeries *series = qobject_cast(m_chart->series().last()); + m_chart->removeSeries(series); + delete series; + series = nullptr; +} + +void MainWidget::removeAllSeries() +{ + if (m_chart->series().isEmpty()) { + qDebug() << "Create a series first"; + return; + } + + detachModelMapper(); + + m_chart->removeAllSeries(); +} + +void MainWidget::addSet() +{ + if (m_chart->series().isEmpty()) { + qDebug() << "Create a series first"; + return; + } + + foreach (QAbstractSeries *s, m_chart->series()) { + QCandlestickSeries *series = qobject_cast(s); + + QDateTime dateTime; + if (series->count()) { + dateTime.setMSecsSinceEpoch(series->candlestickSets().last()->timestamp()); + dateTime = dateTime.addMonths(1); + } else { + dateTime.setDate(QDate(QDateTime::currentDateTime().date().year(), 1, 1)); + dateTime.setTime(QTime(12, 34, 56, 789)); + } + + QCandlestickSet *set = randomSet(dateTime.toMSecsSinceEpoch()); + series->append(set); + } + + updateAxes(); +} + +void MainWidget::insertSet() +{ + if (m_chart->series().isEmpty()) { + qDebug() << "Create a series first"; + return; + } + + foreach (QAbstractSeries *s, m_chart->series()) { + QCandlestickSeries *series = qobject_cast(s); + + QDateTime dateTime; + if (series->count()) { + dateTime.setMSecsSinceEpoch(series->candlestickSets().first()->timestamp()); + dateTime = dateTime.addMonths(-1); + } else { + dateTime.setDate(QDate(QDateTime::currentDateTime().date().year(), 1, 1)); + dateTime.setTime(QTime(12, 34, 56, 789)); + } + + QCandlestickSet *set = randomSet(dateTime.toMSecsSinceEpoch()); + series->insert(0, set); + } + + updateAxes(); +} + +void MainWidget::removeSet() +{ + if (m_chart->series().isEmpty()) { + qDebug() << "Create a series first"; + return; + } + + foreach (QAbstractSeries *s, m_chart->series()) { + QCandlestickSeries *series = qobject_cast(s); + if (series->candlestickSets().isEmpty()) + qDebug() << "Create a set first"; + else + series->remove(series->candlestickSets().last()); + } + + updateAxes(); +} + +void MainWidget::removeAllSets() +{ + if (m_chart->series().isEmpty()) { + qDebug() << "Create a series first"; + return; + } + + foreach (QAbstractSeries *s, m_chart->series()) { + QCandlestickSeries *series = qobject_cast(s); + if (series->candlestickSets().isEmpty()) + qDebug() << "Create a set first"; + else + series->clear(); + } + + updateAxes(); +} + +void MainWidget::changeMaximumColumnWidth(double width) +{ + m_maximumColumnWidth = width; + foreach (QAbstractSeries *s, m_chart->series()) { + QCandlestickSeries *series = qobject_cast(s); + series->setMaximumColumnWidth(m_maximumColumnWidth); + } +} + +void MainWidget::changeMinimumColumnWidth(double width) +{ + m_minimumColumnWidth = width; + foreach (QAbstractSeries *s, m_chart->series()) { + QCandlestickSeries *series = qobject_cast(s); + series->setMinimumColumnWidth(m_minimumColumnWidth); + } +} + +void MainWidget::bodyOutlineVisibleToggled(bool visible) +{ + m_bodyOutlineVisible = visible; + foreach (QAbstractSeries *s, m_chart->series()) { + QCandlestickSeries *series = qobject_cast(s); + series->setBodyOutlineVisible(m_bodyOutlineVisible); + } +} + +void MainWidget::capsVisibleToggled(bool visible) +{ + m_capsVisible = visible; + foreach (QAbstractSeries *s, m_chart->series()) { + QCandlestickSeries *series = qobject_cast(s); + series->setCapsVisible(m_capsVisible); + } +} + +void MainWidget::changeBodyWidth(double width) +{ + m_bodyWidth = width; + foreach (QAbstractSeries *s, m_chart->series()) { + QCandlestickSeries *series = qobject_cast(s); + series->setBodyWidth(m_bodyWidth); + } +} + +void MainWidget::changeCapsWidth(double width) +{ + m_capsWidth = width; + foreach (QAbstractSeries *s, m_chart->series()) { + QCandlestickSeries *series = qobject_cast(s); + series->setCapsWidth(m_capsWidth); + } +} + +void MainWidget::customIncreasingColorToggled(bool custom) +{ + m_customIncreasingColor = custom; + + if (m_chart->series().isEmpty()) + return; + + QCandlestickSeries *series = qobject_cast(m_chart->series().at(0)); + if (series) { + QColor color = m_customIncreasingColor ? QColor(Qt::green) : QColor(); + series->setIncreasingColor(color); + } +} + +void MainWidget::customDecreasingColorToggled(bool custom) +{ + m_customDecreasingColor = custom; + + if (m_chart->series().isEmpty()) + return; + + QCandlestickSeries *series = qobject_cast(m_chart->series().at(0)); + if (series) { + QColor color = m_customDecreasingColor ? QColor(Qt::red) : QColor(); + series->setDecreasingColor(color); + } +} + +void MainWidget::antialiasingToggled(bool enabled) +{ + m_chartView->setRenderHint(QPainter::Antialiasing, enabled); +} + +void MainWidget::animationToggled(bool enabled) +{ + if (enabled) + m_chart->setAnimationOptions(QChart::SeriesAnimations); + else + m_chart->setAnimationOptions(QChart::NoAnimation); +} + +void MainWidget::legendToggled(bool visible) +{ + m_chart->legend()->setVisible(visible); + if (visible) + m_chart->legend()->setAlignment(Qt::AlignBottom); +} + +void MainWidget::titleToggled(bool visible) +{ + if (visible) + m_chart->setTitle(QStringLiteral("Candlestick Chart")); + else + m_chart->setTitle(QString()); +} + +void MainWidget::changeChartTheme(int themeIndex) +{ + if (themeIndex < QChart::ChartThemeLight || themeIndex > QChart::ChartThemeQt) { + qDebug() << "Invalid chart theme index:" << themeIndex; + return; + } + + m_chart->setTheme((QChart::ChartTheme)(themeIndex)); +} + +void MainWidget::changeAxisX(int axisXIndex) +{ + if (m_axisX) { + m_chart->removeAxis(m_axisX); + delete m_axisX; + } + + switch (axisXIndex) { + case 0: + m_axisX = new QBarCategoryAxis(); + break; + case 1: + m_axisX = new QDateTimeAxis(); + break; + case 2: + m_axisX = new QValueAxis(); + break; + default: + qDebug() << "Invalid axis x index:" << axisXIndex; + return; + } + + m_chart->addAxis(m_axisX, Qt::AlignBottom); + + updateAxes(); + + foreach (QAbstractSeries *series, m_chart->series()) + series->attachAxis(m_axisX); +} + +void MainWidget::attachModelMapper() +{ + if (m_hModelMapper->series()) { + qDebug() << "Model mapper is already attached"; + return; + } + + if (m_chart->series().isEmpty()) + addSeries(); + + QCandlestickSeries *series = qobject_cast(m_chart->series().at(0)); + Q_ASSERT(series); + series->setName(QStringLiteral("SWMM")); // Series With Model Mapper + + CustomTableModel *model = qobject_cast(m_hModelMapper->model()); + foreach (QCandlestickSet *set, series->candlestickSets()) + model->addRow(set); + + m_hModelMapper->setFirstCandlestickSetRow(0); + m_hModelMapper->setLastCandlestickSetRow(model->rowCount() - 1); + m_hModelMapper->setSeries(series); +} + +void MainWidget::detachModelMapper() +{ + if (!m_hModelMapper->series()) + return; + + QCandlestickSeries *series = qobject_cast(m_hModelMapper->series()); + Q_ASSERT(series); + series->setName(QStringLiteral("S1")); + + m_hModelMapper->setSeries(nullptr); + m_hModelMapper->setFirstCandlestickSetRow(-1); + m_hModelMapper->setLastCandlestickSetRow(-1); + + CustomTableModel *model = qobject_cast(m_hModelMapper->model()); + model->clearRows(); +} diff --git a/tests/manual/candlesticktester/mainwidget.h b/tests/manual/candlesticktester/mainwidget.h new file mode 100644 index 0000000..4044515 --- /dev/null +++ b/tests/manual/candlesticktester/mainwidget.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Charts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MAINWIDGET_H +#define MAINWIDGET_H + +#include +#include + +QT_BEGIN_NAMESPACE +class QGridLayout; +QT_END_NAMESPACE + +QT_CHARTS_BEGIN_NAMESPACE +class QCandlestickSet; +class QHCandlestickModelMapper; +QT_CHARTS_END_NAMESPACE + +QT_CHARTS_USE_NAMESPACE + +class MainWidget : public QWidget +{ + Q_OBJECT + +public: + explicit MainWidget(QWidget *parent = nullptr); + ~MainWidget(); + +private: + QGridLayout *createSeriesControlsLayout(); + QGridLayout *createSetsControlsLayout(); + QGridLayout *createCandlestickControlsLayout(); + QGridLayout *createMiscellaneousControlsLayout(); + QGridLayout *createModelMapperControlsLayout(); + + qreal randomValue(int min, int max) const; + QCandlestickSet *randomSet(qreal timestamp); + + void updateAxes(); + +private slots: + void addSeries(); + void removeSeries(); + void removeAllSeries(); + void addSet(); + void insertSet(); + void removeSet(); + void removeAllSets(); + void changeMaximumColumnWidth(double width); + void changeMinimumColumnWidth(double width); + void bodyOutlineVisibleToggled(bool visible); + void capsVisibleToggled(bool visible); + void changeBodyWidth(double width); + void changeCapsWidth(double width); + void customIncreasingColorToggled(bool custom); + void customDecreasingColorToggled(bool custom); + void antialiasingToggled(bool enabled); + void animationToggled(bool enabled); + void legendToggled(bool visible); + void titleToggled(bool visible); + void changeChartTheme(int themeIndex); + void changeAxisX(int axisXIndex); + void attachModelMapper(); + void detachModelMapper(); + +private: + QChart *m_chart; + QChartView *m_chartView; + QAbstractAxis *m_axisX; + QAbstractAxis *m_axisY; + qreal m_maximumColumnWidth; + qreal m_minimumColumnWidth; + bool m_bodyOutlineVisible; + bool m_capsVisible; + qreal m_bodyWidth; + qreal m_capsWidth; + bool m_customIncreasingColor; + bool m_customDecreasingColor; + QHCandlestickModelMapper *m_hModelMapper; +}; + +#endif // MAINWIDGET_H diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro index 25545ae..0e02e8c 100644 --- a/tests/manual/manual.pro +++ b/tests/manual/manual.pro @@ -2,7 +2,8 @@ TEMPLATE = subdirs SUBDIRS += \ presenterchart \ polarcharttest \ - boxplottester + boxplottester \ + candlesticktester contains(QT_CONFIG, opengl) { SUBDIRS += chartwidgettest \