/**************************************************************************** ** ** 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 #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)) { 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 { if (min > max) qSwap(min, max); return QRandomGenerator::bounded(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->sets().isEmpty()) { if (m_axisX->type() == QAbstractAxis::AxisTypeBarCategory) { QBarCategoryAxis *axisX = qobject_cast(m_axisX); QStringList categories; for (int i = 0; i < series->sets().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->sets().first()->timestamp() - msInMonth; qreal max = series->sets().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->sets().count(); ++i) { QCandlestickSet *set = randomSet(s->sets().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->sets().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->sets().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->sets().isEmpty()) qDebug() << "Create a set first"; else series->remove(series->sets().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->sets().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->sets()) model->addRow(set); m_hModelMapper->setFirstSetRow(0); m_hModelMapper->setLastSetRow(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->setFirstSetRow(-1); m_hModelMapper->setLastSetRow(-1); CustomTableModel *model = qobject_cast(m_hModelMapper->model()); model->clearRows(); }