diff options
Diffstat (limited to 'src/charts/boxplotchart/qboxplotseries.cpp')
-rw-r--r-- | src/charts/boxplotchart/qboxplotseries.cpp | 690 |
1 files changed, 690 insertions, 0 deletions
diff --git a/src/charts/boxplotchart/qboxplotseries.cpp b/src/charts/boxplotchart/qboxplotseries.cpp new file mode 100644 index 00000000..2f444b40 --- /dev/null +++ b/src/charts/boxplotchart/qboxplotseries.cpp @@ -0,0 +1,690 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Enterprise Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qboxplotseries.h" +#include "qboxplotseries_p.h" +#include "qboxplotlegendmarker.h" +#include "qbarcategoryaxis.h" +#include "boxplotchartitem_p.h" +#include "chartdataset_p.h" +#include "charttheme_p.h" +#include "qvalueaxis.h" +#include "charttheme_p.h" +#include "boxplotanimation_p.h" +#include "qchart_p.h" +#include "qboxset.h" +#include "qboxset_p.h" + +QT_CHARTS_BEGIN_NAMESPACE + +/*! + \class QBoxPlotSeries + \inmodule Qt Charts + \brief Series for creating box-and-whiskers chart. + \mainclass + + QBoxPlotSeries represents a series of data shown as box-and-whisker bars. The purpose of this class is to act as + a container for single box-and-whisker items. Each item is drawn to own slot. If chart includes multiple instances of + QBoxPlotSeries then box-and-whiskers items with the same index are drawn to same slot. + + See the \l {Box and Whiskers Example} {box-and-whiskers chart example} to learn how to create a box-and-whiskers chart. + \image examples_boxplotchart.png + + \sa QBoxSet +*/ +/*! + \fn QBoxPlotSeries::boxsetsAdded(QList<QBoxSet *> sets) + \brief Signal is emitted when a new \a sets of box-and-whiskers data is added to the series. +*/ +/*! + \fn QBoxPlotSeries::boxsetsRemoved(QList<QBoxSet *> sets) + \brief Signal is emitted when \a sets of box-and-whiskers data is removed from the series. +*/ +/*! + \fn QBoxPlotSeries::clicked(QBoxSet *boxset) + \brief Signal is emitted when the user clicks the \a boxset on the chart. +*/ +/*! + \fn QBoxPlotSeries::hovered(bool status, QBoxSet *boxset) + \brief Signal is emitted when there is change in hover \a status over \a boxset. +*/ +/*! + \fn QBoxPlotSeries::countChanged() + \brief Signal is emitted when there is change in count of box-and-whiskers items in the series. +*/ +/*! + \property QBoxPlotSeries::boxOutlineVisible + \brief This property configures the visibility of the middle box outline. +*/ +/*! + \property QBoxPlotSeries::boxWidth + \brief This property configures the width of the box-and-whiskers item. The value signifies the relative + width of the box-and-whiskers item inside its own slot. The value can between 0.0 and 1.0. Negative values + are clamped to 0.0 and values over 1.0 are clamped to 1.0. +*/ +/*! + \property QBoxPlotSeries::pen + \brief This property configures the pen of the box-and-whiskers items. +*/ +/*! + \property QBoxPlotSeries::brush + \brief This property configures the brush of the box-and-whiskers items. +*/ +/*! + \property QBoxPlotSeries::count + \brief The count of sets in series. +*/ + +/*! + \qmlproperty QString BoxPlotSeries::brushFilename + The name of the file used as a brush for the series. +*/ + +/*! + \fn void QBoxPlotSeries::boxOutlineVisibilityChanged() + Signal is emitted when the middle box outline visibility is changed. +*/ +/*! + \fn void QBoxPlotSeries::boxWidthChanged() + Signal is emitted when the width of the box-and-whiskers item is changed. +*/ +/*! + \fn void QBoxPlotSeries::penChanged() + This signal is emitted when the pen of the box-and-whiskers has changed. + \sa brush +*/ +/*! + \fn void QBoxPlotSeries::brushChanged() + This signal is emitted when the brush of the box-and-whiskers has changed. + \sa brush +*/ +/*! + \fn virtual SeriesType QBoxPlotSeries::type() const + \brief Returns type of series. + \sa QAbstractSeries, SeriesType +*/ + +/*! + Constructs empty QBoxPlotSeries. + QBoxPlotSeries is QObject which is a child of a \a parent. +*/ +QBoxPlotSeries::QBoxPlotSeries(QObject *parent) + : QAbstractSeries(*new QBoxPlotSeriesPrivate(this), parent) +{ +} + +/*! + Destructor. Removes series from chart. +*/ +QBoxPlotSeries::~QBoxPlotSeries() +{ + Q_D(QBoxPlotSeries); + if (d->m_chart) + d->m_chart->removeSeries(this); +} + +/*! + Adds a single box and whiskers set to series. Takes ownership of the \a set. If the set is null or is already in series, it won't be appended. + Returns true, if appending succeeded. +*/ +bool QBoxPlotSeries::append(QBoxSet *set) +{ + Q_D(QBoxPlotSeries); + + bool success = d->append(set); + if (success) { + QList<QBoxSet *> sets; + sets.append(set); + set->setParent(this); + emit boxsetsAdded(sets); + emit countChanged(); + } + return success; +} + +/*! + Removes boxset from the series. Deletes the \a set and returns true if successful. +*/ +bool QBoxPlotSeries::remove(QBoxSet *set) +{ + Q_D(QBoxPlotSeries); + bool success = d->remove(set); + if (success) { + QList<QBoxSet *> sets; + sets.append(set); + set->setParent(0); + emit boxsetsRemoved(sets); + emit countChanged(); + delete set; + set = 0; + } + return success; +} + +/*! + Takes a single \a set from the series. Does not delete the boxset object. + + NOTE: The series remains as the boxset's parent object. You must set the + parent object to take full ownership. + + Returns true if take was successful. +*/ +bool QBoxPlotSeries::take(QBoxSet *set) +{ + Q_D(QBoxPlotSeries); + + bool success = d->remove(set); + if (success) { + QList<QBoxSet *> sets; + sets.append(set); + emit boxsetsRemoved(sets); + emit countChanged(); + } + return success; +} + +/*! + Adds a list of boxsets to series. Takes ownership of the \a sets. + Returns true, if all sets were appended successfully. If any of the sets is null or is already appended to series, + nothing is appended and function returns false. If any of the sets is in list more than once, nothing is appended + and function returns false. +*/ +bool QBoxPlotSeries::append(QList<QBoxSet *> sets) +{ + Q_D(QBoxPlotSeries); + bool success = d->append(sets); + if (success) { + emit boxsetsAdded(sets); + emit countChanged(); + } + return success; +} + +/*! + Insert a box-and-whiskers set to the series at \a index postion. Takes ownership of the \a set. If the set is null or + is already in series, it won't be appended. Returns true, if inserting succeeded. + +*/ +bool QBoxPlotSeries::insert(int index, QBoxSet *set) +{ + Q_D(QBoxPlotSeries); + bool success = d->insert(index, set); + if (success) { + QList<QBoxSet *> sets; + sets.append(set); + emit boxsetsAdded(sets); + emit countChanged(); + } + return success; +} + +/*! + Removes all boxsets from the series. Deletes removed sets. +*/ +void QBoxPlotSeries::clear() +{ + Q_D(QBoxPlotSeries); + QList<QBoxSet *> sets = boxSets(); + bool success = d->remove(sets); + if (success) { + emit boxsetsRemoved(sets); + emit countChanged(); + foreach (QBoxSet *set, sets) + delete set; + } +} + +/*! + Returns number of sets in series. +*/ +int QBoxPlotSeries::count() const +{ + Q_D(const QBoxPlotSeries); + return d->m_boxSets.count(); +} + +/*! + Returns a list of sets in series. Keeps ownership of sets. + */ +QList<QBoxSet *> QBoxPlotSeries::boxSets() const +{ + Q_D(const QBoxPlotSeries); + return d->m_boxSets; +} + +/* + Returns QAbstractSeries::SeriesTypeBoxPlot. +*/ +QAbstractSeries::SeriesType QBoxPlotSeries::type() const +{ + return QAbstractSeries::SeriesTypeBoxPlot; +} + +void QBoxPlotSeries::setBoxOutlineVisible(bool visible) +{ + Q_D(QBoxPlotSeries); + + if (d->m_boxOutlineVisible != visible) { + d->m_boxOutlineVisible = visible; + emit d->updated(); + emit boxOutlineVisibilityChanged(); + } +} + +bool QBoxPlotSeries::boxOutlineVisible() +{ + Q_D(QBoxPlotSeries); + + return d->m_boxOutlineVisible; +} + +void QBoxPlotSeries::setBoxWidth(qreal width) +{ + Q_D(QBoxPlotSeries); + + if (width != d->m_boxWidth) { + if (width < 0.0) + width = 0.0; + if (width > 1.0) + width = 1.0; + d->m_boxWidth = width; + emit d->updatedLayout(); + emit boxWidthChanged(); + } +} + +qreal QBoxPlotSeries::boxWidth() +{ + Q_D(QBoxPlotSeries); + + return d->m_boxWidth; +} + +void QBoxPlotSeries::setBrush(const QBrush &brush) +{ + Q_D(QBoxPlotSeries); + + if (d->m_brush != brush) { + d->m_brush = brush; + emit d->updated(); + emit brushChanged(); + } +} + +QBrush QBoxPlotSeries::brush() const +{ + Q_D(const QBoxPlotSeries); + + return d->m_brush; +} + +void QBoxPlotSeries::setPen(const QPen &pen) +{ + Q_D(QBoxPlotSeries); + + if (d->m_pen != pen) { + d->m_pen = pen; + emit d->updated(); + emit penChanged(); + } +} + +QPen QBoxPlotSeries::pen() const +{ + Q_D(const QBoxPlotSeries); + + return d->m_pen; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +QBoxPlotSeriesPrivate::QBoxPlotSeriesPrivate(QBoxPlotSeries *q) + : QAbstractSeriesPrivate(q), + m_pen(QChartPrivate::defaultPen()), + m_brush(QChartPrivate::defaultBrush()), + m_boxOutlineVisible(true), + m_boxWidth(0.5) +{ +} + +QBoxPlotSeriesPrivate::~QBoxPlotSeriesPrivate() +{ + disconnect(this, 0, 0, 0); +} + +void QBoxPlotSeriesPrivate::initializeDomain() +{ + qreal minX(domain()->minX()); + qreal minY(domain()->minY()); + qreal maxX(domain()->maxX()); + qreal maxY(domain()->maxY()); + + qreal x = m_boxSets.count(); + minX = qMin(minX, qreal(-0.5)); + minY = qMin(minY, min()); + maxX = qMax(maxX, x - qreal(0.5)); + maxY = qMax(maxY, max()); + + domain()->setRange(minX, maxX, minY, maxY); +} + +void QBoxPlotSeriesPrivate::initializeAxes() +{ + foreach (QAbstractAxis* axis, m_axes) { + if (axis->type() == QAbstractAxis::AxisTypeBarCategory) { + if (axis->orientation() == Qt::Horizontal) + populateCategories(qobject_cast<QBarCategoryAxis *>(axis)); + } + } +} + +QAbstractAxis::AxisType QBoxPlotSeriesPrivate::defaultAxisType(Qt::Orientation orientation) const +{ + if (orientation == Qt::Horizontal) + return QAbstractAxis::AxisTypeBarCategory; + + return QAbstractAxis::AxisTypeValue; +} + +QAbstractAxis* QBoxPlotSeriesPrivate::createDefaultAxis(Qt::Orientation orientation) const +{ + if (defaultAxisType(orientation) == QAbstractAxis::AxisTypeBarCategory) + return new QBarCategoryAxis; + else + return new QValueAxis; +} + +void QBoxPlotSeriesPrivate::populateCategories(QBarCategoryAxis *axis) +{ + QStringList categories; + if (axis->categories().isEmpty()) { + for (int i(1); i < m_boxSets.count() + 1; i++) { + QBoxSet *set = m_boxSets.at(i - 1); + if (set->label().isEmpty()) + categories << presenter()->numberToString(i); + else + categories << set->label(); + } + axis->append(categories); + } +} + +void QBoxPlotSeriesPrivate::initializeGraphics(QGraphicsItem *parent) +{ + Q_Q(QBoxPlotSeries); + + BoxPlotChartItem *boxPlot = new BoxPlotChartItem(q, parent); + m_item.reset(boxPlot); + 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*)) ); + + QList<QAbstractSeries *> serieses = m_chart->series(); + + // Tries to find this series from the Chart's list of series and deduce the index + int index = 0; + foreach (QAbstractSeries *s, serieses) { + if (s->type() == QAbstractSeries::SeriesTypeBoxPlot) { + if (q == static_cast<QBoxPlotSeries *>(s)) { + boxPlot->m_seriesIndex = index; + m_index = index; + } + index++; + } + } + boxPlot->m_seriesCount = index; + } + + // Make BoxPlotChartItem to instantiate box & whisker items + boxPlot->handleDataStructureChanged(); +} + +void QBoxPlotSeriesPrivate::initializeTheme(int index, ChartTheme* theme, bool forced) +{ + Q_Q(QBoxPlotSeries); + + const QList<QGradient> gradients = theme->seriesGradients(); + + if (forced || QChartPrivate::defaultBrush() == m_brush) { + QColor brushColor = ChartThemeManager::colorAt(gradients.at(index % gradients.size()), 0.5); + q->setBrush(brushColor); + } + + if (forced || QChartPrivate::defaultPen() == m_pen) { + QPen pen = theme->outlinePen(); + pen.setCosmetic(true); + q->setPen(pen); + } +} + +void QBoxPlotSeriesPrivate::initializeAnimations(QChart::AnimationOptions options) +{ + BoxPlotChartItem *item = static_cast<BoxPlotChartItem *>(m_item.data()); + Q_ASSERT(item); + if (item->animation()) + item->animation()->stopAndDestroyLater(); + + if (options.testFlag(QChart::SeriesAnimations)) + m_animation = new BoxPlotAnimation(item); + else + m_animation = 0; + item->setAnimation(m_animation); + + QAbstractSeriesPrivate::initializeAnimations(options); +} + +QList<QLegendMarker*> QBoxPlotSeriesPrivate::createLegendMarkers(QLegend *legend) +{ + Q_Q(QBoxPlotSeries); + QList<QLegendMarker *> list; + return list << new QBoxPlotLegendMarker(q, legend); +} + +void QBoxPlotSeriesPrivate::handleSeriesRemove(QAbstractSeries *series) +{ + Q_Q(QBoxPlotSeries); + + QBoxPlotSeries *removedSeries = static_cast<QBoxPlotSeries *>(series); + + if (q == removedSeries && m_animation) { + m_animation->stopAll(); + QObject::disconnect(m_chart->d_ptr->m_dataset, 0, removedSeries->d_func(), 0); + } + + // Test if series removed is me, then don't do anything + if (q != removedSeries) { + BoxPlotChartItem *item = static_cast<BoxPlotChartItem *>(m_item.data()); + if (item) { + item->m_seriesCount = item->m_seriesCount - 1; + if (removedSeries->d_func()->m_index < m_index) { + m_index--; + item->m_seriesIndex = m_index; + } + + item->handleDataStructureChanged(); + } + } +} + +void QBoxPlotSeriesPrivate::handleSeriesChange(QAbstractSeries *series) +{ + Q_UNUSED(series); + + Q_Q(QBoxPlotSeries); + + BoxPlotChartItem *boxPlot = static_cast<BoxPlotChartItem *>(m_item.data()); + + if (m_chart) { + QList<QAbstractSeries *> serieses = m_chart->series(); + + // Tries to find this series from the Chart's list of series and deduce the index + int index = 0; + foreach (QAbstractSeries *s, serieses) { + if (s->type() == QAbstractSeries::SeriesTypeBoxPlot) { + if (q == static_cast<QBoxPlotSeries *>(s)) { + boxPlot->m_seriesIndex = index; + m_index = index; + } + index++; + } + } + boxPlot->m_seriesCount = index; + } + + boxPlot->handleDataStructureChanged(); +} + +bool QBoxPlotSeriesPrivate::append(QBoxSet *set) +{ + if (m_boxSets.contains(set) || (set == 0) || set->d_ptr->m_series) + return false; // Fail if set is already in list or set is null. + + m_boxSets.append(set); + QObject::connect(set->d_ptr.data(), SIGNAL(updatedLayout()), this, SIGNAL(updatedLayout())); + QObject::connect(set->d_ptr.data(), SIGNAL(updatedBox()), this, SIGNAL(updatedBoxes())); + QObject::connect(set->d_ptr.data(), SIGNAL(restructuredBox()), this, SIGNAL(restructuredBoxes())); + set->d_ptr->m_series = this; + + emit restructuredBoxes(); // this notifies boxplotchartitem + return true; +} + +bool QBoxPlotSeriesPrivate::remove(QBoxSet *set) +{ + if (!m_boxSets.contains(set)) + return false; // Fail if set is not in list + + set->d_ptr->m_series = 0; + m_boxSets.removeOne(set); + QObject::disconnect(set->d_ptr.data(), SIGNAL(updatedLayout()), this, SIGNAL(updatedLayout())); + QObject::disconnect(set->d_ptr.data(), SIGNAL(updatedBox()), this, SIGNAL(updatedBoxes())); + QObject::disconnect(set->d_ptr.data(), SIGNAL(restructuredBox()), this, SIGNAL(restructuredBoxes())); + + emit restructuredBoxes(); // this notifies boxplotchartitem + return true; +} + +bool QBoxPlotSeriesPrivate::append(QList<QBoxSet *> sets) +{ + foreach (QBoxSet *set, sets) { + if ((set == 0) || m_boxSets.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 same set is more than once in given list. + } + + foreach (QBoxSet *set, sets) { + m_boxSets.append(set); + QObject::connect(set->d_ptr.data(), SIGNAL(updatedLayout()), this, SIGNAL(updatedLayout())); + QObject::connect(set->d_ptr.data(), SIGNAL(updatedBox()), this, SIGNAL(updatedBoxes())); + QObject::connect(set->d_ptr.data(), SIGNAL(restructuredBox()), this, SIGNAL(restructuredBoxes())); + set->d_ptr->m_series = this; + } + + emit restructuredBoxes(); // this notifies boxplotchartitem + return true; +} + +bool QBoxPlotSeriesPrivate::remove(QList<QBoxSet *> sets) +{ + if (sets.count() == 0) + return false; + + foreach (QBoxSet *set, sets) { + if ((set == 0) || (!m_boxSets.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 same set is more than once in given list. + } + + foreach (QBoxSet *set, sets) { + set->d_ptr->m_series = 0; + m_boxSets.removeOne(set); + QObject::disconnect(set->d_ptr.data(), SIGNAL(updatedLayout()), this, SIGNAL(updatedLayout())); + QObject::disconnect(set->d_ptr.data(), SIGNAL(updatedBox()), this, SIGNAL(updatedBoxes())); + QObject::disconnect(set->d_ptr.data(), SIGNAL(restructuredBox()), this, SIGNAL(restructuredBoxes())); + } + + emit restructuredBoxes(); // this notifies boxplotchartitem + + return true; +} + +bool QBoxPlotSeriesPrivate::insert(int index, QBoxSet *set) +{ + if ((m_boxSets.contains(set)) || (set == 0) || set->d_ptr->m_series) + return false; // Fail if set is already in list or set is null. + + m_boxSets.insert(index, set); + set->d_ptr->m_series = this; + QObject::connect(set->d_ptr.data(), SIGNAL(updatedLayout()), this, SIGNAL(updatedLayout())); + QObject::connect(set->d_ptr.data(), SIGNAL(updatedBox()), this, SIGNAL(updatedBoxes())); + QObject::connect(set->d_ptr.data(), SIGNAL(restructuredBox()), this, SIGNAL(restructuredBoxes())); + + emit restructuredBoxes(); // this notifies boxplotchartitem + return true; +} + +QBoxSet *QBoxPlotSeriesPrivate::boxSetAt(int index) +{ + return m_boxSets.at(index); +} + +qreal QBoxPlotSeriesPrivate::min() +{ + if (m_boxSets.count() <= 0) + return 0; + + qreal min = m_boxSets.at(0)->at(0); + + foreach (QBoxSet *set, m_boxSets) { + for (int i = 0; i < 5; i++) { + if (set->at(i) < min) + min = set->at(i); + } + } + + return min; +} + +qreal QBoxPlotSeriesPrivate::max() +{ + if (m_boxSets.count() <= 0) + return 0; + + qreal max = m_boxSets.at(0)->at(0); + + foreach (QBoxSet *set, m_boxSets) { + for (int i = 0; i < 5; i++) { + if (set->at(i) > max) + max = set->at(i); + } + } + + return max; +} + +#include "moc_qboxplotseries.cpp" +#include "moc_qboxplotseries_p.cpp" + +QT_CHARTS_END_NAMESPACE + |