diff --git a/src/Chart.cpp b/src/Chart.cpp index 1ec4d4e..5b01032 100644 --- a/src/Chart.cpp +++ b/src/Chart.cpp @@ -1,136 +1,151 @@ /* * This file is part of KQuickCharts * SPDX-FileCopyrightText: 2019 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "Chart.h" #include "datasource/ChartDataSource.h" Chart::Chart(QQuickItem *parent) : QQuickItem(parent) { setFlag(ItemHasContents, true); } ChartDataSource *Chart::nameSource() const { return m_nameSource; } void Chart::setNameSource(ChartDataSource *nameSource) { if (m_nameSource == nameSource) { return; } m_nameSource = nameSource; Q_EMIT nameSourceChanged(); } +ChartDataSource *Chart::shortNameSource() const +{ + return m_shortNameSource; +} + +void Chart::setShortNameSource(ChartDataSource *shortNameSource) +{ + if (m_shortNameSource == shortNameSource) { + return; + } + + m_shortNameSource = shortNameSource; + Q_EMIT shortNameSourceChanged(); +} + ChartDataSource *Chart::colorSource() const { return m_colorSource; } void Chart::setColorSource(ChartDataSource *colorSource) { if (m_colorSource == colorSource) { return; } m_colorSource = colorSource; Q_EMIT colorSourceChanged(); } Chart::DataSourcesProperty Chart::valueSourcesProperty() { return DataSourcesProperty{this, this, &Chart::appendSource, &Chart::sourceCount, &Chart::source, &Chart::clearSources}; } QVector Chart::valueSources() const { return m_valueSources; } void Chart::insertValueSource(int index, ChartDataSource *source) { if (index < 0) { return; } m_valueSources.insert(index, source); connect(source, &QObject::destroyed, this, qOverload(&Chart::removeValueSource)); connect(source, &ChartDataSource::dataChanged, this, &Chart::onDataChanged); onDataChanged(); Q_EMIT valueSourcesChanged(); } void Chart::removeValueSource(int index) { if (index < 0 || index >= m_valueSources.count()) { return; } m_valueSources.at(index)->disconnect(this); m_valueSources.remove(index); onDataChanged(); Q_EMIT valueSourcesChanged(); } void Chart::removeValueSource(QObject *source) { removeValueSource(m_valueSources.indexOf(qobject_cast(source))); } Chart::IndexingMode Chart::indexingMode() const { return m_indexingMode; } void Chart::setIndexingMode(IndexingMode newIndexingMode) { if (newIndexingMode == m_indexingMode) { return; } m_indexingMode = newIndexingMode; onDataChanged(); Q_EMIT indexingModeChanged(); } void Chart::componentComplete() { QQuickItem::componentComplete(); onDataChanged(); } void Chart::appendSource(Chart::DataSourcesProperty *list, ChartDataSource *source) { auto chart = reinterpret_cast(list->data); chart->m_valueSources.append(source); QObject::connect(source, &ChartDataSource::dataChanged, chart, &Chart::onDataChanged); chart->onDataChanged(); } int Chart::sourceCount(Chart::DataSourcesProperty *list) { return reinterpret_cast(list->data)->m_valueSources.count(); } ChartDataSource *Chart::source(Chart::DataSourcesProperty *list, int index) { return reinterpret_cast(list->data)->m_valueSources.at(index); } void Chart::clearSources(Chart::DataSourcesProperty *list) { auto chart = reinterpret_cast(list->data); std::for_each(chart->m_valueSources.cbegin(), chart->m_valueSources.cend(), [chart](ChartDataSource *source) { source->disconnect(chart); }); chart->m_valueSources.clear(); chart->onDataChanged(); } diff --git a/src/Chart.h b/src/Chart.h index eaf3f75..a32f6d4 100644 --- a/src/Chart.h +++ b/src/Chart.h @@ -1,97 +1,106 @@ /* * This file is part of KQuickCharts * SPDX-FileCopyrightText: 2019 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef CHART_H #define CHART_H #include class ChartDataSource; /** * Abstract base class for all charts. */ class Chart : public QQuickItem { Q_OBJECT /** * The data source to use for names of chart items. */ Q_PROPERTY(ChartDataSource *nameSource READ nameSource WRITE setNameSource NOTIFY nameSourceChanged) + /** + * The data source to use for short names of chart items. + */ + Q_PROPERTY(ChartDataSource *shortNameSource READ shortNameSource WRITE setShortNameSource NOTIFY shortNameSourceChanged) /** * The data source to use for colors of chart items. */ Q_PROPERTY(ChartDataSource *colorSource READ colorSource WRITE setColorSource NOTIFY colorSourceChanged) /** * The data sources providing the data this chart needs to render. */ Q_PROPERTY(QQmlListProperty valueSources READ valueSourcesProperty NOTIFY valueSourcesChanged) /** * The indexing mode used for indexing colors and names. */ Q_PROPERTY(IndexingMode indexingMode READ indexingMode WRITE setIndexingMode NOTIFY indexingModeChanged) public: using DataSourcesProperty = QQmlListProperty; /** * How to index color and name sources relative to the different value sources. */ enum IndexingMode { IndexSourceValues = 1, ///< Index each value, restart indexing for each value source. IndexEachSource, ///< Index each value source, never index individual values. IndexAllValues ///< Index each value, continuing with the index for each value source. }; Q_ENUM(IndexingMode) explicit Chart(QQuickItem *parent = nullptr); ~Chart() override = default; ChartDataSource *nameSource() const; void setNameSource(ChartDataSource *nameSource); Q_SIGNAL void nameSourceChanged(); + ChartDataSource *shortNameSource() const; + void setShortNameSource(ChartDataSource *shortNameSource); + Q_SIGNAL void shortNameSourceChanged(); + ChartDataSource *colorSource() const; void setColorSource(ChartDataSource *colorSource); Q_SIGNAL void colorSourceChanged(); DataSourcesProperty valueSourcesProperty(); QVector valueSources() const; Q_SIGNAL void valueSourcesChanged(); Q_INVOKABLE void insertValueSource(int index, ChartDataSource *source); Q_INVOKABLE void removeValueSource(int index); Q_INVOKABLE void removeValueSource(QObject *source); IndexingMode indexingMode() const; void setIndexingMode(IndexingMode newIndexingMode); Q_SIGNAL void indexingModeChanged(); protected: /** * Called when the data of a value source changes. * * This method should be reimplemented by subclasses. It is called whenever * the data of one of the value sources changes. Subclasses should use this * to make sure that they update whatever internal state they use for * rendering, then call update() to schedule rendering the item. */ virtual void onDataChanged() = 0; void componentComplete() override; private: static void appendSource(DataSourcesProperty *list, ChartDataSource *source); static int sourceCount(DataSourcesProperty *list); static ChartDataSource *source(DataSourcesProperty *list, int index); static void clearSources(DataSourcesProperty *list); ChartDataSource *m_nameSource = nullptr; + ChartDataSource *m_shortNameSource = nullptr; ChartDataSource *m_colorSource = nullptr; QVector m_valueSources; IndexingMode m_indexingMode = IndexEachSource; }; #endif // CHART_H diff --git a/src/decorations/LegendModel.cpp b/src/decorations/LegendModel.cpp index c8b5d77..62e9e9e 100644 --- a/src/decorations/LegendModel.cpp +++ b/src/decorations/LegendModel.cpp @@ -1,255 +1,271 @@ /* * This file is part of KQuickCharts * SPDX-FileCopyrightText: 2019 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "LegendModel.h" #include "Chart.h" #include "datasource/ChartDataSource.h" LegendModel::LegendModel(QObject *parent) : QAbstractListModel(parent) { } QHash LegendModel::roleNames() const { static QHash names = { {NameRole, "name"}, + {ShortNameRole, "shortName"}, {ColorRole, "color"}, {ValueRole, "value"}, }; return names; } int LegendModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_items.size(); } QVariant LegendModel::data(const QModelIndex &index, int role) const { if (!checkIndex(index, CheckIndexOption::ParentIsInvalid | CheckIndexOption::IndexIsValid)) return QVariant{}; switch (role) { case NameRole: return m_items.at(index.row()).name; + case ShortNameRole: + return m_items.at(index.row()).shortName; case ColorRole: return m_items.at(index.row()).color; case ValueRole: return m_items.at(index.row()).value; } return QVariant{}; } Chart *LegendModel::chart() const { return m_chart; } void LegendModel::setChart(Chart *newChart) { if (newChart == m_chart) { return; } if (m_chart) { for (const auto &connection : qAsConst(m_connections)) { disconnect(connection); } m_connections.clear(); } m_chart = newChart; queueUpdate(); Q_EMIT chartChanged(); } int LegendModel::sourceIndex() const { return m_sourceIndex; } void LegendModel::setSourceIndex(int index) { if (index == m_sourceIndex) { return; } m_sourceIndex = index; queueUpdate(); Q_EMIT sourceIndexChanged(); } void LegendModel::queueUpdate() { if (!m_updateQueued) { m_updateQueued = true; QMetaObject::invokeMethod(this, &LegendModel::update, Qt::QueuedConnection); } } void LegendModel::queueDataChange() { if (!m_dataChangeQueued) { m_dataChangeQueued = true; QMetaObject::invokeMethod(this, &LegendModel::updateData, Qt::QueuedConnection); } } void LegendModel::update() { m_updateQueued = false; if (!m_chart) return; beginResetModel(); m_items.clear(); ChartDataSource *colorSource = m_chart->colorSource(); ChartDataSource *nameSource = m_chart->nameSource(); + ChartDataSource *shortNameSource = m_chart->shortNameSource(); m_connections.push_back(connect(m_chart, &Chart::colorSourceChanged, this, &LegendModel::queueUpdate, Qt::UniqueConnection)); m_connections.push_back(connect(m_chart, &Chart::nameSourceChanged, this, &LegendModel::queueUpdate, Qt::UniqueConnection)); auto sources = m_chart->valueSources(); int itemCount = countItems(); std::transform(sources.cbegin(), sources.cend(), std::back_inserter(m_connections), [this](ChartDataSource *source) { return connect(source, &ChartDataSource::dataChanged, this, &LegendModel::queueDataChange, Qt::UniqueConnection); }); m_connections.push_back(connect(m_chart, &Chart::valueSourcesChanged, this, &LegendModel::queueUpdate, Qt::UniqueConnection)); - if ((!colorSource && !nameSource) || itemCount <= 0) { + if ((!colorSource && !nameSource && !shortNameSource) || itemCount <= 0) { endResetModel(); return; } if (colorSource) { m_connections.push_back(connect(colorSource, &ChartDataSource::dataChanged, this, &LegendModel::queueDataChange, Qt::UniqueConnection)); } if (nameSource) { m_connections.push_back(connect(nameSource, &ChartDataSource::dataChanged, this, &LegendModel::queueDataChange, Qt::UniqueConnection)); } + if (shortNameSource) { + m_connections.push_back(connect(shortNameSource, &ChartDataSource::dataChanged, this, &LegendModel::queueDataChange, Qt::UniqueConnection)); + } + for (int i = 0; i < itemCount; ++i) { LegendItem item; item.name = nameSource ? nameSource->item(i).toString() : QString(); + item.shortName = shortNameSource ? shortNameSource->item(i).toString() : QString(); item.color = colorSource ? colorSource->item(i).value() : QColor(); item.value = getValueForItem(i); m_items.push_back(item); } endResetModel(); } void LegendModel::updateData() { ChartDataSource *colorSource = m_chart->colorSource(); ChartDataSource *nameSource = m_chart->nameSource(); + ChartDataSource *shortNameSource = m_chart->shortNameSource(); auto itemCount = countItems(); m_dataChangeQueued = false; if (itemCount != int(m_items.size())) { // Number of items changed, so trigger a full update queueUpdate(); return; } QVector> changedRows(itemCount); std::for_each(m_items.begin(), m_items.end(), [&, i = 0](LegendItem &item) mutable { auto name = nameSource ? nameSource->item(i).toString() : QString{}; if (item.name != name) { item.name = name; changedRows[i] << NameRole; } + auto shortName = shortNameSource ? shortNameSource->item(i).toString() : QString{}; + if (item.shortName != shortName) { + item.shortName = shortName; + changedRows[i] << NameRole; + } + auto color = colorSource ? colorSource->item(i).toString() : QColor{}; if (item.color != color) { item.color = color; changedRows[i] << ColorRole; } auto value = getValueForItem(i); if (item.value != value) { item.value = value; changedRows[i] << ValueRole; } i++; }); for(auto i = 0; i < changedRows.size(); ++i) { auto changedRoles = changedRows.at(i); if (!changedRoles.isEmpty()) { Q_EMIT dataChanged(index(i, 0), index(i, 0), changedRoles); } } } int LegendModel::countItems() { auto sources = m_chart->valueSources(); int itemCount = 0; switch (m_chart->indexingMode()) { case Chart::IndexSourceValues: if (sources.count() > 0) { itemCount = sources.at(0)->itemCount(); } break; case Chart::IndexEachSource: itemCount = sources.count(); break; case Chart::IndexAllValues: itemCount = std::accumulate(sources.cbegin(), sources.cend(), 0, [](int current, ChartDataSource *source) -> int { return current + source->itemCount(); }); break; } return itemCount; } QVariant LegendModel::getValueForItem(int item) { const auto sources = m_chart->valueSources(); auto value = QVariant{}; switch (m_chart->indexingMode()) { case Chart::IndexSourceValues: value = sources.at(0)->item(item); break; case Chart::IndexEachSource: value = sources.at(item)->item(0); break; case Chart::IndexAllValues: for (auto source : sources) { if (source->itemCount() < item) { item -= source->itemCount(); } else { value = source->item(item); break; } } break; } return value; } diff --git a/src/decorations/LegendModel.h b/src/decorations/LegendModel.h index ba138e5..e54d42c 100644 --- a/src/decorations/LegendModel.h +++ b/src/decorations/LegendModel.h @@ -1,71 +1,72 @@ /* * This file is part of KQuickCharts * SPDX-FileCopyrightText: 2019 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef LEGENDMODEL_H #define LEGENDMODEL_H #include #include #include class Chart; class ChartDataSource; struct LegendItem { QString name; + QString shortName; QColor color; QVariant value; }; /** * A model that extracts information from a chart that can be displayed as a legend. */ class LegendModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(Chart *chart READ chart WRITE setChart NOTIFY chartChanged) Q_PROPERTY(int sourceIndex READ sourceIndex WRITE setSourceIndex NOTIFY sourceIndexChanged) public: - enum Roles { NameRole = Qt::UserRole, ColorRole, ValueRole }; + enum Roles { NameRole = Qt::UserRole, ShortNameRole, ColorRole, ValueRole }; enum SourceIndex { UseSourceCount = -2 }; Q_ENUM(SourceIndex) explicit LegendModel(QObject *parent = nullptr); QHash roleNames() const override; int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; Chart *chart() const; void setChart(Chart *newChart); Q_SIGNAL void chartChanged(); int sourceIndex() const; void setSourceIndex(int index); Q_SIGNAL void sourceIndexChanged(); private: void queueUpdate(); void queueDataChange(); void update(); void updateData(); int countItems(); QVariant getValueForItem(int item); Chart *m_chart = nullptr; int m_sourceIndex = UseSourceCount; bool m_updateQueued = false; bool m_dataChangeQueued = false; std::vector m_connections; std::vector m_items; }; #endif // LEGENDMODEL_H