AdvancedFeaturesDemo Example

#include "mainwindow.h"

#include <QApplication>
#include <QGroupBox>
#include <QComboBox>
#include <QCheckBox>
#include <QMessageBox>
#include <QPainter>
#include <QFormLayout>
#include <QDateTime>

#include <qmath.h>

/* AdvancedChart */
class AdvancedChart : public Qtitan::Chart
{
public:
    AdvancedChart(QWidget* parent = 0);

protected:
    virtual void paintEvent(QPaintEvent* event);
    virtual void resizeEvent(QResizeEvent* event);
public:
    qreal m_XLine;
    QVector<QPointF> m_arMarkers;
};

AdvancedChart::AdvancedChart(QWidget* parent)
    : Qtitan::Chart(parent)
{
}

static const QSizeF m_szMarker(5.0, 5.0);

void AdvancedChart::paintEvent(QPaintEvent* event)
{
    Qtitan::Chart::paintEvent(event);
    QPainter p(this);
    // Draw markers:
    for (int i = 0; i < m_arMarkers.size(); i++)
    {
        ChartSeries2D* series2D = (ChartSeries2D*)series().at(i);
        QColor color = /*series().at(i)->color();*/series2D->color();

        ChartView2D* view = static_cast<ChartView2D *>(series2D->view());
        ChartAxis* axisY = view->axisY();
        Q_ASSERT(axisY != Q_NULL);

        QRect rectAxisBounds = axisY->boundingRect();
        rectAxisBounds.setWidth(view->boundingRect().width());
        QPointF ptMarker = m_arMarkers[i];

        if (rectAxisBounds.contains(ptMarker.toPoint()))
        {
            QPainter::RenderHints hints = p.renderHints();
            p.setRenderHints(hints, false);
            p.setRenderHint(QPainter::Antialiasing);

            const QPen savePen = p.pen();
            p.setPen(QPen(color.darker()));

            QBrush saveBrush = p.brush();
            color.setAlpha(140);
            p.setBrush(color);

            p.drawEllipse(ptMarker, m_szMarker.width(), m_szMarker.height());
            p.setBrush(saveBrush);
            p.setPen(savePen);

            p.setRenderHints(p.renderHints(), false);
            p.setRenderHints(hints, true);
        }
    }

    // Draw vertical line:
    if (m_XLine != -1.0)
    {
        const QPen savePen = p.pen();
        p.setPen(QPen(QBrush(QColor(Qt::red)), 1.0, Qt::DashLine));

        QRect rect = viewRect();
        p.drawLine(m_XLine, rect.top(), m_XLine, rect.bottom());

        p.setPen(savePen);
    }
}

void AdvancedChart::resizeEvent(QResizeEvent* event)
{
    Qtitan::Chart::resizeEvent(event);
    m_arMarkers.clear();
    m_XLine = -1.;
}

#define DEFAULT_INFO    tr("Move the mouse cursor over the chart and see information on hovered chart component")
/* MainWindow */
MainWindow::MainWindow()
    : DemoChartWindow(tr("Advanced Features"))
{
    setChart(new AdvancedChart());
    createSeriesParametrs();
    seriesChanged(m_seriesSwitcher->currentIndex());

    connect(m_chart, SIGNAL(chartMouseTrack(ChartHitInfo*)), this, SLOT(mouseTrack(ChartHitInfo*)), Qt::DirectConnection);
    connect(m_chart, SIGNAL(chartMouseUp(ChartHitInfo*)), this, SLOT(mouseUp(ChartHitInfo*)), Qt::DirectConnection);
}

void MainWindow::createSeriesParametrs()
{
    // Option Series
    QGroupBox* seriesTypeGroup = createGroupParameters(tr("Series"));
    QFormLayout* localLayout = (QFormLayout*)seriesTypeGroup->layout();
    m_seriesSwitcher = new QComboBox(seriesTypeGroup);
    m_seriesSwitcher->addItem(tr("Combined"), QVariant(Combined));
    m_seriesSwitcher->addItem(tr("Secondary Axis"), QVariant(SecondaryAxis));
    m_seriesSwitcher->addItem(tr("Interactive"), QVariant(Interactive));
    connect(m_seriesSwitcher, SIGNAL(currentIndexChanged(int)), this, SLOT(seriesChanged(int)));

    // SecondaryAxis
    m_showSecondaryAxisX = new QCheckBox(tr("Secondary X Axis"), seriesTypeGroup);
    m_showSecondaryAxisX->setCheckState(Qt::Checked);
    m_showSecondaryAxisX->setVisible(false);
    connect(m_showSecondaryAxisX, SIGNAL(stateChanged(int)), this, SLOT(showSecondaryAxisXChanged(int)));

    m_showSecondaryAxisY = new QCheckBox(tr("Secondary Y Axis"), seriesTypeGroup);
    m_showSecondaryAxisY->setCheckState(Qt::Checked);
    m_showSecondaryAxisY->setVisible(false);
    connect(m_showSecondaryAxisY, SIGNAL(stateChanged(int)), this, SLOT(showSecondaryAxisYChanged(int)));

    // Option Labels
    createLabelsGroup();
    m_dataLabelsGroup->setVisible(false);
    connect(m_angleDataLabelsSwitcher, SIGNAL(currentIndexChanged(int)), this, SLOT(updateAngleChanged(int)));

    m_hitTestResult = new QLabel(DEFAULT_INFO, seriesTypeGroup);
    m_hitTestResult->setWordWrap(true);
    m_hitTestResult->setVisible(false);
    m_hitTestResult->setMinimumWidth(180);
    m_hitTestResult->setMaximumWidth(180);
    m_hitTestResult->setMinimumHeight(100);
    m_hitTestResult->setMaximumHeight(100);

    localLayout->addRow(m_seriesSwitcher);
    localLayout->addRow(m_showSecondaryAxisX);
    localLayout->addRow(m_showSecondaryAxisY);
    localLayout->addRow(m_hitTestResult);
}

void MainWindow::createCombined()
{
    createTitle(tr("Area, Bar and Line"));

    m_chart->legend()->setVisible(true);
    m_chart->legend()->setVerticalAlignment(ChartLegend::LegendCenter);
    //
    ChartAreaSeries2D* series1 = new ChartAreaSeries2D();
    series1->setName(tr("Area"));
    m_chart->appendSeries(series1);

    ChartBarSeries2D* series2 = new ChartBarSeries2D();
    series2->setTransparency(180);
    series2->setName(tr("Bar"));
    m_chart->appendSeries(series2);

    ChartLineSeries2D* series3 = new ChartLineSeries2D();
    series3->setName(tr("Line"));
    m_chart->appendSeries(series3);

    qreal arTemp[3][12] = {
        // 0     1        2      3      4      5      6      7       8      9      10    11
        { 28.5,  29.5,   24.5,  29.0,  32.6,  34.5,  36.5,  28.0,  34.6,  36.0,  40.0,  39.0 },  // 0 ChartAreaSeries2D
        { 23.5,  28.0,   34.0,  32.0,  31.0,  25.0,  39.0,  40.0,  42.0,  39.0,  45.0,  50.0 },  // 1 ChartBarSeries2D
        { 3.0,   5.0,    9.0,   13.0,  17.0,  21.0,  25.0,  29.0,  36.0,  42.0,  48.0,  54.0 }}; // 2 ChartLineSeries2D

    for (int month = 1; month <= 12; month++)
    {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
        QString strMonth = QLocale::system().monthName(month, QLocale::ShortFormat);
#else
        QString strMonth = QDate::shortMonthName(month);
#endif
        series1->addAxisPointY(arTemp[0][month - 1], strMonth);
        series2->addAxisPointY(arTemp[1][month - 1], strMonth);
        series3->addAxisPointY(arTemp[2][month - 1], strMonth);
    }
}

void MainWindow::createSecondaryAxis()
{
    m_chart->legend()->setVisible(true);
    m_chart->legend()->setVerticalAlignment(ChartLegend::LegendCenter);

    ChartSplineSeries2D* series1 = new ChartSplineSeries2D();
    series1->setName(tr("Series1"));
    m_chart->appendSeries(series1);
    series1->setMarkerType(ChartMarker::Triangle);
    series1->setMarkerSize(16);

    series1->addXY(1, 0.0);
    series1->addXY(2, 2.0);
    series1->addXY(3, 5.0);
    series1->addXY(4, 3.0);
    series1->addXY(5, 3.5);
    series1->addXY(6, 5.0);
    series1->addXY(7, 8.0);
    series1->addXY(8, 7.0);

    ChartSplineSeries2D* series2 = new ChartSplineSeries2D();
    series2->setName(tr("Series2"));
    m_chart->appendSeries(series2);
    series2->setMarkerType(ChartMarker::Star);
    series2->setMarkerSize(16);

    series2->addXY(1, 2.0);
    series2->addXY(2, 6.0);
    series2->addXY(3, 12.0);
    series2->addXY(4, 16.0);
    series2->addXY(5, 14.0);
    series2->addXY(6, 9.0);
    series2->addXY(7, 6.0);
    series2->addXY(8, 2.0);
}

void MainWindow::createInteractive()
{
    createTitle(tr("Weather Report"));

    m_chart->setAreasOrientation(Qt::Vertical);
    m_chart->legend()->setVisible(true);
    m_chart->legend()->setVerticalAlignment(ChartLegend::LegendCenter);

    ChartView2D* view1 = new ChartView2D();
    m_chart->appendView(view1);
    m_axisY1 = view1->axisY();
    ChartAxisTitle* titleY = m_axisY1->title();
    titleY->setText(tr("Pressure"));
    titleY->setVisible(true);
    view1->axisY()->setAutoRange(false);
    view1->axisY()->setFixedRange(15, 25);
    view1->axisX()->setVisible(false);

    ChartView2D* view2 = new ChartView2D();
    m_chart->appendView(view2);
    titleY = view2->axisY()->title();
    titleY->setText(tr("Temperature"));
    titleY->setVisible(true);
    view2->axisY()->setAutoRange(false);
    view2->axisY()->setFixedRange(1012, 1025);
    view2->axisX()->setVisible(false);

    ChartView2D* view3 = new ChartView2D();
    m_chart->appendView(view3);
    m_axisY2 = view3->axisY();
    titleY = m_axisY2->title();
    titleY->setText(tr("Humidity,%"));
    titleY->setVisible(true);
    view3->axisX()->title()->setText(tr("Days"));
    view3->axisX()->title()->setVisible(true);

    // Add series and data points:
    ChartLineSeries2D* series1 = new ChartLineSeries2D();
    series1->setName(tr("Temperature"));
    series1->setMarkerVisible(false);
    series1->label()->setVisible(true);
    ((ChartPointSeriesLabel*)series1->label())->setAngle(-270);
    view1->appendSeries(series1);

    ChartAreaSeries2D* series2 = new ChartAreaSeries2D();
    series2->setName(tr("Pressure"));
    series2->setMarkerVisible(false);
    series2->label()->setVisible(false);
    view2->appendSeries(series2);

    ChartBarSeries2D* series3 = new ChartBarSeries2D();
    series3->setName(tr("Humidity"));
    series3->label()->setVisible(false);
    view3->appendSeries(series3);

    for (int day = 1; day <= 31; day++)
    {
        series1->addXY(day, (int)(20.0 + qtn_rand(-5.0, 5.0)));
        series2->addXY(day, 1020.0 + qtn_rand(-4.0, 4.0));
        series3->addXY(day, 50.0 + qtn_rand(-40.0, 40.0));
    }
}

void MainWindow::updateValueParameters()
{
    m_dataLabelsGroup->setVisible(false);
    DemoChartWindow::updateValueParameters();

    int indexChart = m_seriesSwitcher->currentIndex();

    if (indexChart == SecondaryAxis)
    {
        m_showSecondaryAxisX->setVisible(true);
        m_showSecondaryAxisY->setVisible(true);

        if (m_showSecondaryAxisX->isVisible() && m_showSecondaryAxisY->isVisible())
        {
            showSecondaryAxisXChanged(m_showSecondaryAxisX->checkState());
            showSecondaryAxisYChanged(m_showSecondaryAxisY->checkState());
        }
    }
    else
    {
        m_showSecondaryAxisX->setVisible(false);
        m_showSecondaryAxisY->setVisible(false);
    }

    if (indexChart == Combined)
    {
        m_dataLabelsGroup->setVisible(true);
        showDataLabels(m_dataLabelsGroup->isChecked());

        if (m_angleDataLabelsSwitcher && m_angleDataLabelsSwitcher->isVisible())
        {
            labelsAngleChanged(m_angleDataLabelsSwitcher->currentIndex());
            const SeriesList& listSeries = m_chart->series();
            for (int i = 0; i < listSeries.count(); i++)
            {
                if (ChartBarSeries2D* series= qobject_cast<ChartBarSeries2D*>(listSeries.at(i)))
                {
                    ChartBarSeriesLabel* label = (ChartBarSeriesLabel*)series->label();
                    label->setPosition(ChartBarSeriesLabel::ChartBarLabelTop);
                }
            }
        }
    }
}

void MainWindow::seriesChanged(int index)
{
    ((AdvancedChart*)m_chart)->m_XLine = -1.;
    ((AdvancedChart*)m_chart)->m_arMarkers.clear();
    m_chart->clearSeries();
    m_chart->clearTitles();
    m_chart->setBackgroundBrush(QBrush());

    QVariant var = m_seriesSwitcher->itemData(index);
    switch((SeriesType)var.toUInt())
    {
        case Combined :
            {
                m_chart->enableMouseTrackingMode(ChartHitInfo::HitNone);

                m_hitTestResult->setVisible(false);
                createCombined();
            }
            break;
        case SecondaryAxis :
            {
                m_chart->enableMouseTrackingMode(ChartHitInfo::HitNone);

                m_hitTestResult->setVisible(false);
                createSecondaryAxis();
            }
            break;
        case Interactive :
            {
                m_chart->enableMouseTrackingMode(ChartHitInfo::HitChartArea | ChartHitInfo::HitTitle | ChartHitInfo::HitDataPoint |
                    ChartHitInfo::HitLegend | ChartHitInfo::HitAxis | ChartHitInfo::HitAxisName | ChartHitInfo::HitView);

                m_hitTestResult->setVisible(true);
                createInteractive();
            }
            break;
        default:
            break;
    }
    updateValueParameters();
}

void MainWindow::showSecondaryAxisXChanged(int state)
{
    ChartView2D* view2D = static_cast<ChartView2D *>(m_chart->views().at(0));

    if (Qt::Checked != state)
        view2D->secondaryAxisX()->setVisible(false);
    else
        showAxisXChanged(Qt::Checked);

    ChartAxisTitle* titleX = view2D->axisX()->title();
    Q_ASSERT(titleX != Q_NULL);

    if (Qt::Checked == state)
    {
        view2D->secondaryAxisX()->title()->setText(tr("Series 2 X"));
        view2D->secondaryAxisX()->title()->setVisible(true);

        titleX->setText(tr("Series 1 X"));
        titleX->setVisible(true);
    }
    else
    {
        titleX->setText(tr("Series 1 and 2 X"));
        titleX->setVisible(true);
    }

    ChartSeries2D* impl = (ChartSeries2D*)m_chart->series().at(1);
    impl->setSecondaryAxisX(Qt::Checked == state);
}

void MainWindow::showAxisXChanged(int state)
{
    ChartView2D* view2D = static_cast<ChartView2D *>(m_chart->views().at(0));
    view2D->secondaryAxisX()->setVisible(Qt::Checked == state);
}

void MainWindow::showSecondaryAxisYChanged(int state)
{
    ChartView2D* view2D = static_cast<ChartView2D *>(m_chart->views().at(0));

    if (Qt::Checked != state)
        view2D->secondaryAxisY()->setVisible(false);
    else
        showAxisYChanged(Qt::Checked);

    ChartAxisTitle* titleY = view2D->axisY()->title();
    Q_ASSERT(titleY != Q_NULL);

    if (Qt::Checked == state)
    {
        view2D->secondaryAxisY()->title()->setText(tr("Series 2 Y"));
        view2D->secondaryAxisY()->title()->setVisible(true);

        titleY->setText(tr("Series 1 Y"));
        titleY->setVisible(true);
    }
    else
    {
        titleY->setText(tr("Series 1 and 2 Y"));
        titleY->setVisible(true);
    }

    ChartSeries2D* impl = (ChartSeries2D*)m_chart->series().at(1);
    impl->setSecondaryAxisX(Qt::Checked == state);
}

void MainWindow::showAxisYChanged(int state)
{
    ChartView2D* view2D = static_cast<ChartView2D *>(m_chart->views().at(0));
    view2D->secondaryAxisY()->setVisible(Qt::Checked == state);
}

void MainWindow::updateAngleChanged(int)
{
    updateValueParameters();
}

void MainWindow::mouseTrack(ChartHitInfo* hit)
{
    const ViewList& listViews = m_chart->views();
    if (hit == Q_NULL || listViews.count() == 0)
        return;
    ChartView2D* view2D = static_cast<ChartView2D*>(listViews.at(0));
    QPoint pt = hit->hitPoint();

    ChartAxis* axisXX = view2D->axisX();
    Q_ASSERT(axisXX != Q_NULL);

    // Get X Position on the chart
    qreal realXMarker = axisXX->valueFromPoint(pt);

    // left marker index
    int xValue = (int)floor(realXMarker);

    bool redraw = false;
    const int seriesCount = m_chart->series().count();
    ((AdvancedChart*)m_chart)->m_arMarkers.resize(seriesCount);

    for (int i = 0; i < seriesCount; i++)
    {
        ChartSeries2D* series2D = (ChartSeries2D*)m_chart->series().at(i);
        Q_ASSERT(series2D != Q_NULL);

        const ChartDataPoint* dp1 = series2D->at(xValue);
        const ChartDataPoint* dp2 = series2D->at(xValue + 1);

        if (dp1 != Q_NULL && dp2 != Q_NULL)
        {
            ChartView2D* view = static_cast<ChartView2D *>(series2D->view());
            ChartAxis* axisX = view->axisX();
            ChartAxis* axisY = view->axisY();

            qreal x1 = axisX->valueToPoint(dp1->valueX());
            qreal x2 = axisX->valueToPoint(dp2->valueX());

            qreal y1 = axisY->valueToPoint(dp1->valueY());
            qreal y2 = axisY->valueToPoint(dp2->valueY());

            QPoint pt1(x1, y1);
            QPoint pt2(x2, y2);

            QPoint ptMarker;
            if (::qFabs((qreal)pt.x() - pt1.x()) < ::qFabs((qreal)pt.x() - pt2.x()))
                ptMarker = pt1;
            else
                ptMarker = pt2;

            if (((AdvancedChart*)m_chart)->m_arMarkers.size() < seriesCount || ((AdvancedChart*)m_chart)->m_arMarkers[i] != ptMarker)
            {
                ((AdvancedChart*)m_chart)->m_arMarkers[i] = ptMarker;
                if (i == 0)
                    ((AdvancedChart*)m_chart)->m_XLine = ptMarker.x();
                redraw = true;
            }
        }
    }

    if (redraw)
        m_chart->repaint();

    QCursor cursor =  Qt::ArrowCursor;
    QString strInfo;
    switch (hit->hitInfo())
    {
    case ChartHitInfo::HitDataPoint:
        {
            strInfo = tr("Hovered Element: Data Point\nClick left mouse button to get additional information");
            cursor = Qt::PointingHandCursor;
        }
        break;
    case ChartHitInfo::HitChartArea:
        strInfo = tr("Hovered Element: Chart Area");
        break;
    case ChartHitInfo::HitAxis:
    case ChartHitInfo::HitAxisName:
        {
            ChartAxis* axis = Q_NULL;
            QString strName = tr("NoName");
            qreal value = 0.0;
            for (int i = 0, count = listViews.count(); i < count && axis == Q_NULL; i++)
            {
                ChartView2D* view = static_cast<ChartView2D *>(listViews.at(i));

                ChartAxis* axisX = view->axisX();
                Q_ASSERT(axisX != Q_NULL);

                ChartAxis* axisY = view->axisY();
                Q_ASSERT(axisY != Q_NULL);

                if (axisX->axisID() == hit->axisID1())
                    axis = axisX;
                else if (axisY->axisID() == hit->axisID1())
                    axis = axisY;
                else if (m_axisY1->axisID() == hit->axisID1())
                    axis = m_axisY1;
                else if (m_axisY2->axisID() == hit->axisID1())
                    axis = m_axisY2;

                if (axis != Q_NULL)
                {
                    strName = axis->title()->text();
                    value = axis->valueFromPoint(pt);
                }

                if (hit->hitInfo() == ChartHitInfo::HitAxisName)
                    strInfo = QStringLiteral("Hovered Element: Axis Name\r\nAxis Name: %1").arg(strName);
                else
                    strInfo = QStringLiteral("Hovered Element: Axis\r\nAxis Name: %1\r\nValue = %2").arg(strName).arg(value);
            }
        }
        break;
    case ChartHitInfo::HitTitle:
            strInfo = tr("Hovered Element: Chart Title");
        break;
    case ChartHitInfo::HitView:
        {
            for (int i = 0, count = listViews.count(); i < count && strInfo.isEmpty(); i++)
            {
                ChartView2D* view = static_cast<ChartView2D*>(listViews.at(i));

                ChartAxis* axisX = view->axis(hit->axisID1());
                ChartAxis* axisY = view->axis(hit->axisID2());

                if (axisX != Q_NULL && axisY != Q_NULL)
                {
                    Q_ASSERT(axisX != Q_NULL);
                    Q_ASSERT(axisY != Q_NULL);
                    int valueX = (int)(axisX->valueFromPoint(pt) + 1.5);
                    qreal valueY = axisY->valueFromPoint(pt);
                    strInfo = QStringLiteral("Hovered Element: Chart View\r\nX Value: %1\r\nY Value: %2").arg(valueX).arg(valueY);
                }
            }
            if (strInfo.isEmpty())
                strInfo = tr("Hovered Element: Chart View");
        }
        break;

    case ChartHitInfo::HitLegend:
            strInfo = tr("Hovered Element: Legend");
        break;
    default:
            strInfo = DEFAULT_INFO;
        break;
    }

    if (!strInfo.isEmpty())
        m_hitTestResult->setText(strInfo);

    setCursor(cursor);
}

void MainWindow::mouseUp(ChartHitInfo* hit)
{
    if (hit->mouseButton() == Qt::NoButton || hit->hitInfo() != ChartHitInfo::HitDataPoint)
        return;

    ChartSeries2D* dataTable = (ChartSeries2D*)m_chart->series().at(hit->index());
    Q_ASSERT(dataTable != Q_NULL);

    QString strInfo;
    strInfo = QStringLiteral("Clicked on the Data Point:\r\n\nSeries Index: %1\r\nSeries Name: %2\r\nData Point Index: %3\r\nData Point Value: %4").
        arg(hit->index()).arg(dataTable->name()).arg(hit->pointIndex()).arg(dataTable->at(hit->pointIndex())->valueY());

    QMessageBox messageBox(QMessageBox::Information, qApp->applicationName(), QString(), QMessageBox::Ok, this);
    messageBox.setInformativeText(strInfo);
    messageBox.exec();
}