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 "AxisLabels.h" |
9 | |
10 | #include <QDebug> |
11 | #include <QQmlContext> |
12 | |
13 | #include "ItemBuilder.h" |
14 | #include "datasource/ChartDataSource.h" |
15 | |
16 | AxisLabelsAttached::AxisLabelsAttached(QObject *parent) |
17 | : QObject(parent) |
18 | { |
19 | } |
20 | |
21 | int AxisLabelsAttached::index() const |
22 | { |
23 | return m_index; |
24 | } |
25 | |
26 | void AxisLabelsAttached::setIndex(int newIndex) |
27 | { |
28 | if (newIndex == m_index) { |
29 | return; |
30 | } |
31 | |
32 | m_index = newIndex; |
33 | Q_EMIT indexChanged(); |
34 | } |
35 | |
36 | QString AxisLabelsAttached::label() const |
37 | { |
38 | return m_label; |
39 | } |
40 | |
41 | void AxisLabelsAttached::setLabel(const QString &newLabel) |
42 | { |
43 | if (newLabel == m_label) { |
44 | return; |
45 | } |
46 | |
47 | m_label = newLabel; |
48 | Q_EMIT labelChanged(); |
49 | } |
50 | |
51 | AxisLabels::AxisLabels(QQuickItem *parent) |
52 | : QQuickItem(parent) |
53 | { |
54 | m_itemBuilder = std::make_unique<ItemBuilder>(); |
55 | connect(sender: m_itemBuilder.get(), signal: &ItemBuilder::finished, context: this, slot: &AxisLabels::polish); |
56 | connect(sender: m_itemBuilder.get(), signal: &ItemBuilder::beginCreate, context: this, slot: &AxisLabels::onBeginCreate); |
57 | } |
58 | |
59 | AxisLabels::~AxisLabels() = default; |
60 | |
61 | AxisLabels::Direction AxisLabels::direction() const |
62 | { |
63 | return m_direction; |
64 | } |
65 | |
66 | void AxisLabels::setDirection(AxisLabels::Direction newDirection) |
67 | { |
68 | if (newDirection == m_direction) { |
69 | return; |
70 | } |
71 | |
72 | m_direction = newDirection; |
73 | polish(); |
74 | Q_EMIT directionChanged(); |
75 | } |
76 | |
77 | QQmlComponent *AxisLabels::delegate() const |
78 | { |
79 | return m_itemBuilder->component(); |
80 | } |
81 | |
82 | void AxisLabels::setDelegate(QQmlComponent *newDelegate) |
83 | { |
84 | if (newDelegate == m_itemBuilder->component()) { |
85 | return; |
86 | } |
87 | |
88 | m_itemBuilder->setComponent(newDelegate); |
89 | updateLabels(); |
90 | Q_EMIT delegateChanged(); |
91 | } |
92 | |
93 | ChartDataSource *AxisLabels::source() const |
94 | { |
95 | return m_source; |
96 | } |
97 | |
98 | void AxisLabels::setSource(ChartDataSource *newSource) |
99 | { |
100 | if (newSource == m_source) { |
101 | return; |
102 | } |
103 | |
104 | if (m_source) { |
105 | m_source->disconnect(receiver: this); |
106 | } |
107 | |
108 | m_source = newSource; |
109 | |
110 | if (m_source) { |
111 | connect(sender: m_source, signal: &ChartDataSource::dataChanged, context: this, slot: [this]() { |
112 | updateLabels(); |
113 | }); |
114 | } |
115 | |
116 | updateLabels(); |
117 | Q_EMIT sourceChanged(); |
118 | } |
119 | |
120 | Qt::Alignment AxisLabels::alignment() const |
121 | { |
122 | return m_alignment; |
123 | } |
124 | |
125 | void AxisLabels::setAlignment(Qt::Alignment newAlignment) |
126 | { |
127 | if (newAlignment == m_alignment) { |
128 | return; |
129 | } |
130 | |
131 | m_alignment = newAlignment; |
132 | polish(); |
133 | Q_EMIT alignmentChanged(); |
134 | } |
135 | |
136 | bool AxisLabels::constrainToBounds() const |
137 | { |
138 | return m_constrainToBounds; |
139 | } |
140 | |
141 | void AxisLabels::setConstrainToBounds(bool newConstrainToBounds) |
142 | { |
143 | if (newConstrainToBounds == m_constrainToBounds) { |
144 | return; |
145 | } |
146 | |
147 | m_constrainToBounds = newConstrainToBounds; |
148 | polish(); |
149 | Q_EMIT constrainToBoundsChanged(); |
150 | } |
151 | |
152 | void AxisLabels::updatePolish() |
153 | { |
154 | if (!m_itemBuilder->isFinished()) { |
155 | return; |
156 | } |
157 | |
158 | auto maxWidth = 0.0; |
159 | auto totalWidth = 0.0; |
160 | auto maxHeight = 0.0; |
161 | auto totalHeight = 0.0; |
162 | |
163 | auto labels = m_itemBuilder->items(); |
164 | for (auto label : labels) { |
165 | maxWidth = std::max(a: maxWidth, b: label->implicitWidth()); |
166 | maxHeight = std::max(a: maxHeight, b: label->implicitHeight()); |
167 | totalWidth += label->implicitWidth(); |
168 | totalHeight += label->implicitHeight(); |
169 | } |
170 | |
171 | auto impWidth = isHorizontal() ? totalWidth : maxWidth; |
172 | auto impHeight = isHorizontal() ? maxHeight : totalHeight; |
173 | |
174 | if (qFuzzyCompare(p1: impWidth, p2: width()) && qFuzzyCompare(p1: impHeight, p2: height())) { |
175 | return; |
176 | } |
177 | |
178 | setImplicitSize(impWidth, impHeight); |
179 | |
180 | auto spacing = (isHorizontal() ? width() : height()) / (labels.size() - 1); |
181 | auto i = 0; |
182 | auto layoutWidth = isHorizontal() ? 0.0 : width(); |
183 | auto layoutHeight = isHorizontal() ? height() : 0.0; |
184 | |
185 | for (auto label : labels) { |
186 | auto x = 0.0; |
187 | auto y = 0.0; |
188 | |
189 | switch (m_direction) { |
190 | case Direction::HorizontalLeftRight: |
191 | x = i * spacing; |
192 | break; |
193 | case Direction::HorizontalRightLeft: |
194 | x = width() - i * spacing; |
195 | break; |
196 | case Direction::VerticalTopBottom: |
197 | y = i * spacing; |
198 | break; |
199 | case Direction::VerticalBottomTop: |
200 | y = height() - i * spacing; |
201 | break; |
202 | } |
203 | |
204 | if (m_alignment & Qt::AlignHCenter) { |
205 | x += (layoutWidth - label->implicitWidth()) / 2; |
206 | } else if (m_alignment & Qt::AlignRight) { |
207 | x += layoutWidth - label->implicitWidth(); |
208 | } |
209 | |
210 | if (m_alignment & Qt::AlignVCenter) { |
211 | y += (layoutHeight - label->implicitHeight()) / 2; |
212 | } else if (m_alignment & Qt::AlignBottom) { |
213 | y += layoutHeight - label->implicitHeight(); |
214 | } |
215 | |
216 | if (m_constrainToBounds) { |
217 | x = std::max(a: x, b: 0.0); |
218 | x = x + label->implicitWidth() > width() ? width() - label->implicitWidth() : x; |
219 | y = std::max(a: y, b: 0.0); |
220 | y = y + label->implicitHeight() > height() ? height() - label->implicitHeight() : y; |
221 | } |
222 | |
223 | label->setX(x); |
224 | label->setY(y); |
225 | |
226 | i++; |
227 | } |
228 | } |
229 | |
230 | void AxisLabels::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) |
231 | { |
232 | QQuickItem::geometryChange(newGeometry, oldGeometry); |
233 | |
234 | if (newGeometry != oldGeometry) { |
235 | polish(); |
236 | } |
237 | } |
238 | |
239 | bool AxisLabels::isHorizontal() |
240 | { |
241 | return m_direction == Direction::HorizontalLeftRight || m_direction == Direction::HorizontalRightLeft; |
242 | } |
243 | |
244 | void AxisLabels::updateLabels() |
245 | { |
246 | m_itemBuilder->clear(); |
247 | |
248 | if (!m_itemBuilder->component() || !m_source) { |
249 | return; |
250 | } |
251 | |
252 | m_itemBuilder->setCount(m_source->itemCount()); |
253 | m_itemBuilder->build(parent: this); |
254 | } |
255 | |
256 | void AxisLabels::onBeginCreate(int index, QQuickItem *item) |
257 | { |
258 | QObject::connect(sender: item, signal: &QQuickItem::implicitWidthChanged, context: this, slot: &AxisLabels::polish); |
259 | QObject::connect(sender: item, signal: &QQuickItem::implicitHeightChanged, context: this, slot: &AxisLabels::polish); |
260 | |
261 | auto attached = static_cast<AxisLabelsAttached *>(qmlAttachedPropertiesObject<AxisLabels>(obj: item, create: true)); |
262 | attached->setIndex(index); |
263 | attached->setLabel(m_source->item(index).toString()); |
264 | } |
265 | |
266 | #include "moc_AxisLabels.cpp" |
267 | |