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
16AxisLabelsAttached::AxisLabelsAttached(QObject *parent)
17 : QObject(parent)
18{
19}
20
21int AxisLabelsAttached::index() const
22{
23 return m_index;
24}
25
26void AxisLabelsAttached::setIndex(int newIndex)
27{
28 if (newIndex == m_index) {
29 return;
30 }
31
32 m_index = newIndex;
33 Q_EMIT indexChanged();
34}
35
36QString AxisLabelsAttached::label() const
37{
38 return m_label;
39}
40
41void 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
51AxisLabels::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::scheduleLayout);
56 connect(sender: m_itemBuilder.get(), signal: &ItemBuilder::beginCreate, context: this, slot: &AxisLabels::onBeginCreate);
57}
58
59AxisLabels::~AxisLabels() = default;
60
61AxisLabels::Direction AxisLabels::direction() const
62{
63 return m_direction;
64}
65
66void AxisLabels::setDirection(AxisLabels::Direction newDirection)
67{
68 if (newDirection == m_direction) {
69 return;
70 }
71
72 m_direction = newDirection;
73 scheduleLayout();
74 Q_EMIT directionChanged();
75}
76
77QQmlComponent *AxisLabels::delegate() const
78{
79 return m_itemBuilder->component();
80}
81
82void 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
93ChartDataSource *AxisLabels::source() const
94{
95 return m_source;
96}
97
98void 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
120Qt::Alignment AxisLabels::alignment() const
121{
122 return m_alignment;
123}
124
125void AxisLabels::setAlignment(Qt::Alignment newAlignment)
126{
127 if (newAlignment == m_alignment) {
128 return;
129 }
130
131 m_alignment = newAlignment;
132 scheduleLayout();
133 Q_EMIT alignmentChanged();
134}
135
136bool AxisLabels::constrainToBounds() const
137{
138 return m_constrainToBounds;
139}
140
141void AxisLabels::setConstrainToBounds(bool newConstrainToBounds)
142{
143 if (newConstrainToBounds == m_constrainToBounds) {
144 return;
145 }
146
147 m_constrainToBounds = newConstrainToBounds;
148 scheduleLayout();
149 Q_EMIT constrainToBoundsChanged();
150}
151void AxisLabels::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
152{
153 QQuickItem::geometryChange(newGeometry, oldGeometry);
154
155 if (newGeometry != oldGeometry) {
156 scheduleLayout();
157 }
158}
159
160void AxisLabels::scheduleLayout()
161{
162 if (!m_layoutScheduled) {
163 auto scheduleLayoutLambda = [this]() {
164 layout();
165 m_layoutScheduled = false;
166 };
167 QMetaObject::invokeMethod(object: this, function&: scheduleLayoutLambda, type: Qt::QueuedConnection);
168 m_layoutScheduled = true;
169 }
170}
171
172bool AxisLabels::isHorizontal()
173{
174 return m_direction == Direction::HorizontalLeftRight || m_direction == Direction::HorizontalRightLeft;
175}
176
177void AxisLabels::updateLabels()
178{
179 m_itemBuilder->clear();
180
181 if (!m_itemBuilder->component() || !m_source) {
182 return;
183 }
184
185 m_itemBuilder->setCount(m_source->itemCount());
186 m_itemBuilder->build(parent: this);
187}
188
189void AxisLabels::layout()
190{
191 if (!m_itemBuilder->isFinished()) {
192 scheduleLayout();
193 return;
194 }
195
196 auto maxWidth = 0.0;
197 auto totalWidth = 0.0;
198 auto maxHeight = 0.0;
199 auto totalHeight = 0.0;
200
201 auto labels = m_itemBuilder->items();
202 for (auto label : labels) {
203 maxWidth = std::max(a: maxWidth, b: label->width());
204 maxHeight = std::max(a: maxHeight, b: label->height());
205 totalWidth += label->width();
206 totalHeight += label->height();
207 }
208
209 auto impWidth = isHorizontal() ? totalWidth : maxWidth;
210 auto impHeight = isHorizontal() ? maxHeight : totalHeight;
211
212 if (qFuzzyCompare(p1: impWidth, p2: width()) && qFuzzyCompare(p1: impHeight, p2: height())) {
213 return;
214 }
215
216 setImplicitWidth(impWidth);
217 setImplicitHeight(impHeight);
218
219 auto spacing = (isHorizontal() ? width() : height()) / (labels.size() - 1);
220 auto i = 0;
221 auto layoutWidth = isHorizontal() ? 0.0 : width();
222 auto layoutHeight = isHorizontal() ? height() : 0.0;
223
224 for (auto label : labels) {
225 auto x = 0.0;
226 auto y = 0.0;
227
228 switch (m_direction) {
229 case Direction::HorizontalLeftRight:
230 x = i * spacing;
231 break;
232 case Direction::HorizontalRightLeft:
233 x = width() - i * spacing;
234 break;
235 case Direction::VerticalTopBottom:
236 y = i * spacing;
237 break;
238 case Direction::VerticalBottomTop:
239 y = height() - i * spacing;
240 break;
241 }
242
243 if (m_alignment & Qt::AlignHCenter) {
244 x += (layoutWidth - label->width()) / 2;
245 } else if (m_alignment & Qt::AlignRight) {
246 x += layoutWidth - label->width();
247 }
248
249 if (m_alignment & Qt::AlignVCenter) {
250 y += (layoutHeight - label->height()) / 2;
251 } else if (m_alignment & Qt::AlignBottom) {
252 y += layoutHeight - label->height();
253 }
254
255 if (m_constrainToBounds) {
256 x = std::max(a: x, b: 0.0);
257 x = x + label->width() > width() ? width() - label->width() : x;
258 y = std::max(a: y, b: 0.0);
259 y = y + label->height() > height() ? height() - label->height() : y;
260 }
261
262 label->setX(x);
263 label->setY(y);
264 i++;
265 }
266}
267
268void AxisLabels::onBeginCreate(int index, QQuickItem *item)
269{
270 QObject::connect(sender: item, signal: &QQuickItem::xChanged, context: this, slot: [this]() {
271 scheduleLayout();
272 });
273 QObject::connect(sender: item, signal: &QQuickItem::yChanged, context: this, slot: [this]() {
274 scheduleLayout();
275 });
276 QObject::connect(sender: item, signal: &QQuickItem::widthChanged, context: this, slot: [this]() {
277 scheduleLayout();
278 });
279 QObject::connect(sender: item, signal: &QQuickItem::heightChanged, context: this, slot: [this]() {
280 scheduleLayout();
281 });
282
283 auto attached = static_cast<AxisLabelsAttached *>(qmlAttachedPropertiesObject<AxisLabels>(obj: item, create: true));
284 attached->setIndex(index);
285 attached->setLabel(m_source->item(index).toString());
286}
287
288#include "moc_AxisLabels.cpp"
289

source code of kquickcharts/controls/AxisLabels.cpp