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 | |
13 | LegendModel::LegendModel(QObject *parent) |
14 | : QAbstractListModel(parent) |
15 | { |
16 | } |
17 | |
18 | QHash<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 | |
30 | int LegendModel::rowCount(const QModelIndex &parent) const |
31 | { |
32 | if (parent.isValid()) { |
33 | return 0; |
34 | } |
35 | |
36 | return m_items.size(); |
37 | } |
38 | |
39 | QVariant 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 | |
59 | Chart *LegendModel::chart() const |
60 | { |
61 | return m_chart; |
62 | } |
63 | |
64 | void 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 | |
82 | int LegendModel::sourceIndex() const |
83 | { |
84 | return m_sourceIndex; |
85 | } |
86 | |
87 | void 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 | |
98 | void 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 | |
106 | void 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 | |
114 | void 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 | |
170 | void 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 | |
224 | int 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 | |
248 | QVariant 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 | |