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 m_connections.push_back(x: connect(sender: m_chart, signal: &Chart::destroyed, context: this, slot: &LegendModel::onChartDestroyed, type: Qt::UniqueConnection));
132
133 auto sources = m_chart->valueSources();
134 int itemCount = countItems();
135
136 std::transform(first: sources.cbegin(), last: sources.cend(), result: std::back_inserter(x&: m_connections), unary_op: [this](ChartDataSource *source) {
137 return connect(sender: source, signal: &ChartDataSource::dataChanged, context: this, slot: &LegendModel::queueDataChange, type: Qt::UniqueConnection);
138 });
139
140 m_connections.push_back(x: connect(sender: m_chart, signal: &Chart::valueSourcesChanged, context: this, slot: &LegendModel::queueUpdate, type: Qt::UniqueConnection));
141
142 if ((!colorSource && !(nameSource || shortNameSource)) || itemCount <= 0) {
143 endResetModel();
144 return;
145 }
146
147 if (colorSource) {
148 m_connections.push_back(x: connect(sender: colorSource, signal: &ChartDataSource::dataChanged, context: this, slot: &LegendModel::queueDataChange, type: Qt::UniqueConnection));
149 }
150
151 if (nameSource) {
152 m_connections.push_back(x: connect(sender: nameSource, signal: &ChartDataSource::dataChanged, context: this, slot: &LegendModel::queueDataChange, type: Qt::UniqueConnection));
153 }
154
155 if (shortNameSource) {
156 m_connections.push_back(x: connect(sender: shortNameSource, signal: &ChartDataSource::dataChanged, context: this, slot: &LegendModel::queueDataChange, type: Qt::UniqueConnection));
157 }
158
159 for (int i = 0; i < itemCount; ++i) {
160 LegendItem item;
161 item.name = nameSource ? nameSource->item(index: i).toString() : QString();
162 item.shortName = shortNameSource ? shortNameSource->item(index: i).toString() : QString();
163 item.color = colorSource ? colorSource->item(index: i).value<QColor>() : QColor();
164 item.value = getValueForItem(item: i);
165 m_items.push_back(x: item);
166 }
167
168 endResetModel();
169}
170
171void LegendModel::updateData()
172{
173 m_dataChangeQueued = false;
174
175 if (!m_chart) {
176 return;
177 }
178
179 ChartDataSource *colorSource = m_chart->colorSource();
180 ChartDataSource *nameSource = m_chart->nameSource();
181 ChartDataSource *shortNameSource = m_chart->shortNameSource();
182
183 auto itemCount = countItems();
184
185 if (itemCount != int(m_items.size())) {
186 // Number of items changed, so trigger a full update
187 queueUpdate();
188 return;
189 }
190
191 QList<QList<int>> changedRows(itemCount);
192
193 std::for_each(first: m_items.begin(), last: m_items.end(), f: [&, i = 0](LegendItem &item) mutable {
194 auto name = nameSource ? nameSource->item(index: i).toString() : QString{};
195 if (item.name != name) {
196 item.name = name;
197 changedRows[i] << NameRole;
198 }
199
200 auto shortName = shortNameSource ? shortNameSource->item(index: i).toString() : QString{};
201 if (item.shortName != shortName) {
202 item.shortName = shortName;
203 changedRows[i] << ShortNameRole;
204 }
205
206 auto color = colorSource ? colorSource->item(index: i).toString() : QColor{};
207 if (item.color != color) {
208 item.color = color;
209 changedRows[i] << ColorRole;
210 }
211
212 auto value = getValueForItem(item: i);
213 if (item.value != value) {
214 item.value = value;
215 changedRows[i] << ValueRole;
216 }
217
218 i++;
219 });
220
221 for (auto i = 0; i < changedRows.size(); ++i) {
222 auto changedRoles = changedRows.at(i);
223 if (!changedRoles.isEmpty()) {
224 Q_EMIT dataChanged(topLeft: index(row: i, column: 0), bottomRight: index(row: i, column: 0), roles: changedRoles);
225 }
226 }
227}
228
229int LegendModel::countItems()
230{
231 auto sources = m_chart->valueSources();
232 int itemCount = 0;
233
234 switch (m_chart->indexingMode()) {
235 case Chart::IndexSourceValues:
236 if (sources.count() > 0) {
237 itemCount = sources.at(i: 0)->itemCount();
238 }
239 break;
240 case Chart::IndexEachSource:
241 itemCount = sources.count();
242 break;
243 case Chart::IndexAllValues:
244 itemCount = std::accumulate(first: sources.cbegin(), last: sources.cend(), init: 0, binary_op: [](int current, ChartDataSource *source) -> int {
245 return current + source->itemCount();
246 });
247 break;
248 }
249
250 return itemCount;
251}
252
253QVariant LegendModel::getValueForItem(int item)
254{
255 const auto sources = m_chart->valueSources();
256 auto value = QVariant{};
257
258 switch (m_chart->indexingMode()) {
259 case Chart::IndexSourceValues:
260 value = sources.at(i: 0)->item(index: item);
261 break;
262 case Chart::IndexEachSource:
263 value = sources.at(i: item)->first();
264 break;
265 case Chart::IndexAllValues:
266 for (auto source : sources) {
267 if (item >= source->itemCount()) {
268 item -= source->itemCount();
269 } else {
270 value = source->item(index: item);
271 break;
272 }
273 }
274 break;
275 }
276
277 return value;
278}
279
280void LegendModel::onChartDestroyed()
281{
282 beginResetModel();
283 m_items.clear();
284 m_chart = nullptr;
285 endResetModel();
286}
287
288#include "moc_LegendModel.cpp"
289

source code of kquickcharts/controls/LegendModel.cpp