1/*
2 * This file is part of KQuickCharts
3 * SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl>
4 *
5 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7
8#include "LegendModel.h"
9
10#include "Chart.h"
11#include "datasource/ChartDataSource.h"
12
13LegendModel::LegendModel(QObject *parent)
14 : QAbstractListModel(parent)
15{
16}
17
18QHash<int, QByteArray> LegendModel::roleNames() const
19{
20 static QHash<int, QByteArray> names = {
21 {NameRole, "name"},
22 {ShortNameRole, "shortName"},
23 {ColorRole, "color"},
24 {ValueRole, "value"},
25 };
26
27 return names;
28}
29
30int LegendModel::rowCount(const QModelIndex &parent) const
31{
32 if (parent.isValid()) {
33 return 0;
34 }
35
36 return m_items.size();
37}
38
39QVariant LegendModel::data(const QModelIndex &index, int role) const
40{
41 if (!checkIndex(index, options: CheckIndexOption::ParentIsInvalid | CheckIndexOption::IndexIsValid)) {
42 return {};
43 }
44
45 switch (role) {
46 case NameRole:
47 return m_items.at(n: index.row()).name;
48 case ShortNameRole:
49 return m_items.at(n: index.row()).shortName;
50 case ColorRole:
51 return m_items.at(n: index.row()).color;
52 case ValueRole:
53 return m_items.at(n: index.row()).value;
54 }
55
56 return QVariant{};
57}
58
59Chart *LegendModel::chart() const
60{
61 return m_chart;
62}
63
64void LegendModel::setChart(Chart *newChart)
65{
66 if (newChart == m_chart) {
67 return;
68 }
69
70 if (m_chart) {
71 for (const auto &connection : std::as_const(t&: m_connections)) {
72 disconnect(connection);
73 }
74 m_connections.clear();
75 }
76
77 m_chart = newChart;
78 queueUpdate();
79 Q_EMIT chartChanged();
80}
81
82int LegendModel::sourceIndex() const
83{
84 return m_sourceIndex;
85}
86
87void LegendModel::setSourceIndex(int index)
88{
89 if (index == m_sourceIndex) {
90 return;
91 }
92
93 m_sourceIndex = index;
94 queueUpdate();
95 Q_EMIT sourceIndexChanged();
96}
97
98void LegendModel::queueUpdate()
99{
100 if (!m_updateQueued) {
101 m_updateQueued = true;
102 QMetaObject::invokeMethod(object: this, function: &LegendModel::update, type: Qt::QueuedConnection);
103 }
104}
105
106void LegendModel::queueDataChange()
107{
108 if (!m_dataChangeQueued) {
109 m_dataChangeQueued = true;
110 QMetaObject::invokeMethod(object: this, function: &LegendModel::updateData, type: Qt::QueuedConnection);
111 }
112}
113
114void LegendModel::update()
115{
116 m_updateQueued = false;
117
118 if (!m_chart) {
119 return;
120 }
121
122 beginResetModel();
123 m_items.clear();
124
125 ChartDataSource *colorSource = m_chart->colorSource();
126 ChartDataSource *nameSource = m_chart->nameSource();
127 ChartDataSource *shortNameSource = m_chart->shortNameSource();
128
129 m_connections.push_back(x: connect(sender: m_chart, signal: &Chart::colorSourceChanged, context: this, slot: &LegendModel::queueUpdate, type: Qt::UniqueConnection));
130 m_connections.push_back(x: connect(sender: m_chart, signal: &Chart::nameSourceChanged, context: this, slot: &LegendModel::queueUpdate, type: Qt::UniqueConnection));
131
132 auto sources = m_chart->valueSources();
133 int itemCount = countItems();
134
135 std::transform(first: sources.cbegin(), last: sources.cend(), result: std::back_inserter(x&: m_connections), unary_op: [this](ChartDataSource *source) {
136 return connect(sender: source, signal: &ChartDataSource::dataChanged, context: this, slot: &LegendModel::queueDataChange, type: Qt::UniqueConnection);
137 });
138
139 m_connections.push_back(x: connect(sender: m_chart, signal: &Chart::valueSourcesChanged, context: this, slot: &LegendModel::queueUpdate, type: Qt::UniqueConnection));
140
141 if ((!colorSource && !(nameSource || shortNameSource)) || itemCount <= 0) {
142 endResetModel();
143 return;
144 }
145
146 if (colorSource) {
147 m_connections.push_back(x: connect(sender: colorSource, signal: &ChartDataSource::dataChanged, context: this, slot: &LegendModel::queueDataChange, type: Qt::UniqueConnection));
148 }
149
150 if (nameSource) {
151 m_connections.push_back(x: connect(sender: nameSource, signal: &ChartDataSource::dataChanged, context: this, slot: &LegendModel::queueDataChange, type: Qt::UniqueConnection));
152 }
153
154 if (shortNameSource) {
155 m_connections.push_back(x: connect(sender: shortNameSource, signal: &ChartDataSource::dataChanged, context: this, slot: &LegendModel::queueDataChange, type: Qt::UniqueConnection));
156 }
157
158 for (int i = 0; i < itemCount; ++i) {
159 LegendItem item;
160 item.name = nameSource ? nameSource->item(index: i).toString() : QString();
161 item.shortName = shortNameSource ? shortNameSource->item(index: i).toString() : QString();
162 item.color = colorSource ? colorSource->item(index: i).value<QColor>() : QColor();
163 item.value = getValueForItem(item: i);
164 m_items.push_back(x: item);
165 }
166
167 endResetModel();
168}
169
170void LegendModel::updateData()
171{
172 ChartDataSource *colorSource = m_chart->colorSource();
173 ChartDataSource *nameSource = m_chart->nameSource();
174 ChartDataSource *shortNameSource = m_chart->shortNameSource();
175
176 auto itemCount = countItems();
177
178 m_dataChangeQueued = false;
179
180 if (itemCount != int(m_items.size())) {
181 // Number of items changed, so trigger a full update
182 queueUpdate();
183 return;
184 }
185
186 QList<QList<int>> changedRows(itemCount);
187
188 std::for_each(first: m_items.begin(), last: m_items.end(), f: [&, i = 0](LegendItem &item) mutable {
189 auto name = nameSource ? nameSource->item(index: i).toString() : QString{};
190 if (item.name != name) {
191 item.name = name;
192 changedRows[i] << NameRole;
193 }
194
195 auto shortName = shortNameSource ? shortNameSource->item(index: i).toString() : QString{};
196 if (item.shortName != shortName) {
197 item.shortName = shortName;
198 changedRows[i] << ShortNameRole;
199 }
200
201 auto color = colorSource ? colorSource->item(index: i).toString() : QColor{};
202 if (item.color != color) {
203 item.color = color;
204 changedRows[i] << ColorRole;
205 }
206
207 auto value = getValueForItem(item: i);
208 if (item.value != value) {
209 item.value = value;
210 changedRows[i] << ValueRole;
211 }
212
213 i++;
214 });
215
216 for (auto i = 0; i < changedRows.size(); ++i) {
217 auto changedRoles = changedRows.at(i);
218 if (!changedRoles.isEmpty()) {
219 Q_EMIT dataChanged(topLeft: index(row: i, column: 0), bottomRight: index(row: i, column: 0), roles: changedRoles);
220 }
221 }
222}
223
224int LegendModel::countItems()
225{
226 auto sources = m_chart->valueSources();
227 int itemCount = 0;
228
229 switch (m_chart->indexingMode()) {
230 case Chart::IndexSourceValues:
231 if (sources.count() > 0) {
232 itemCount = sources.at(i: 0)->itemCount();
233 }
234 break;
235 case Chart::IndexEachSource:
236 itemCount = sources.count();
237 break;
238 case Chart::IndexAllValues:
239 itemCount = std::accumulate(first: sources.cbegin(), last: sources.cend(), init: 0, binary_op: [](int current, ChartDataSource *source) -> int {
240 return current + source->itemCount();
241 });
242 break;
243 }
244
245 return itemCount;
246}
247
248QVariant LegendModel::getValueForItem(int item)
249{
250 const auto sources = m_chart->valueSources();
251 auto value = QVariant{};
252
253 switch (m_chart->indexingMode()) {
254 case Chart::IndexSourceValues:
255 value = sources.at(i: 0)->item(index: item);
256 break;
257 case Chart::IndexEachSource:
258 value = sources.at(i: item)->first();
259 break;
260 case Chart::IndexAllValues:
261 for (auto source : sources) {
262 if (item >= source->itemCount()) {
263 item -= source->itemCount();
264 } else {
265 value = source->item(index: item);
266 break;
267 }
268 }
269 break;
270 }
271
272 return value;
273}
274
275#include "moc_LegendModel.cpp"
276

source code of kquickcharts/controls/LegendModel.cpp