File indexing completed on 2024-04-21 03:56:41
0001 /* 0002 * This file is part of KQuickCharts 0003 * SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "AxisLabels.h" 0009 0010 #include <QDebug> 0011 #include <QQmlContext> 0012 0013 #include "ItemBuilder.h" 0014 #include "datasource/ChartDataSource.h" 0015 0016 AxisLabelsAttached::AxisLabelsAttached(QObject *parent) 0017 : QObject(parent) 0018 { 0019 } 0020 0021 int AxisLabelsAttached::index() const 0022 { 0023 return m_index; 0024 } 0025 0026 void AxisLabelsAttached::setIndex(int newIndex) 0027 { 0028 if (newIndex == m_index) { 0029 return; 0030 } 0031 0032 m_index = newIndex; 0033 Q_EMIT indexChanged(); 0034 } 0035 0036 QString AxisLabelsAttached::label() const 0037 { 0038 return m_label; 0039 } 0040 0041 void AxisLabelsAttached::setLabel(const QString &newLabel) 0042 { 0043 if (newLabel == m_label) { 0044 return; 0045 } 0046 0047 m_label = newLabel; 0048 Q_EMIT labelChanged(); 0049 } 0050 0051 AxisLabels::AxisLabels(QQuickItem *parent) 0052 : QQuickItem(parent) 0053 { 0054 m_itemBuilder = std::make_unique<ItemBuilder>(); 0055 connect(m_itemBuilder.get(), &ItemBuilder::finished, this, &AxisLabels::scheduleLayout); 0056 connect(m_itemBuilder.get(), &ItemBuilder::beginCreate, this, &AxisLabels::onBeginCreate); 0057 } 0058 0059 AxisLabels::~AxisLabels() = default; 0060 0061 AxisLabels::Direction AxisLabels::direction() const 0062 { 0063 return m_direction; 0064 } 0065 0066 void AxisLabels::setDirection(AxisLabels::Direction newDirection) 0067 { 0068 if (newDirection == m_direction) { 0069 return; 0070 } 0071 0072 m_direction = newDirection; 0073 scheduleLayout(); 0074 Q_EMIT directionChanged(); 0075 } 0076 0077 QQmlComponent *AxisLabels::delegate() const 0078 { 0079 return m_itemBuilder->component(); 0080 } 0081 0082 void AxisLabels::setDelegate(QQmlComponent *newDelegate) 0083 { 0084 if (newDelegate == m_itemBuilder->component()) { 0085 return; 0086 } 0087 0088 m_itemBuilder->setComponent(newDelegate); 0089 updateLabels(); 0090 Q_EMIT delegateChanged(); 0091 } 0092 0093 ChartDataSource *AxisLabels::source() const 0094 { 0095 return m_source; 0096 } 0097 0098 void AxisLabels::setSource(ChartDataSource *newSource) 0099 { 0100 if (newSource == m_source) { 0101 return; 0102 } 0103 0104 if (m_source) { 0105 m_source->disconnect(this); 0106 } 0107 0108 m_source = newSource; 0109 0110 if (m_source) { 0111 connect(m_source, &ChartDataSource::dataChanged, this, [this]() { 0112 updateLabels(); 0113 }); 0114 } 0115 0116 updateLabels(); 0117 Q_EMIT sourceChanged(); 0118 } 0119 0120 Qt::Alignment AxisLabels::alignment() const 0121 { 0122 return m_alignment; 0123 } 0124 0125 void AxisLabels::setAlignment(Qt::Alignment newAlignment) 0126 { 0127 if (newAlignment == m_alignment) { 0128 return; 0129 } 0130 0131 m_alignment = newAlignment; 0132 scheduleLayout(); 0133 Q_EMIT alignmentChanged(); 0134 } 0135 0136 bool AxisLabels::constrainToBounds() const 0137 { 0138 return m_constrainToBounds; 0139 } 0140 0141 void AxisLabels::setConstrainToBounds(bool newConstrainToBounds) 0142 { 0143 if (newConstrainToBounds == m_constrainToBounds) { 0144 return; 0145 } 0146 0147 m_constrainToBounds = newConstrainToBounds; 0148 scheduleLayout(); 0149 Q_EMIT constrainToBoundsChanged(); 0150 } 0151 void AxisLabels::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) 0152 { 0153 QQuickItem::geometryChange(newGeometry, oldGeometry); 0154 0155 if (newGeometry != oldGeometry) { 0156 scheduleLayout(); 0157 } 0158 } 0159 0160 void AxisLabels::scheduleLayout() 0161 { 0162 if (!m_layoutScheduled) { 0163 auto scheduleLayoutLambda = [this]() { 0164 layout(); 0165 m_layoutScheduled = false; 0166 }; 0167 QMetaObject::invokeMethod(this, scheduleLayoutLambda, Qt::QueuedConnection); 0168 m_layoutScheduled = true; 0169 } 0170 } 0171 0172 bool AxisLabels::isHorizontal() 0173 { 0174 return m_direction == Direction::HorizontalLeftRight || m_direction == Direction::HorizontalRightLeft; 0175 } 0176 0177 void AxisLabels::updateLabels() 0178 { 0179 m_itemBuilder->clear(); 0180 0181 if (!m_itemBuilder->component() || !m_source) { 0182 return; 0183 } 0184 0185 m_itemBuilder->setCount(m_source->itemCount()); 0186 m_itemBuilder->build(this); 0187 } 0188 0189 void AxisLabels::layout() 0190 { 0191 if (!m_itemBuilder->isFinished()) { 0192 scheduleLayout(); 0193 return; 0194 } 0195 0196 auto maxWidth = 0.0; 0197 auto totalWidth = 0.0; 0198 auto maxHeight = 0.0; 0199 auto totalHeight = 0.0; 0200 0201 auto labels = m_itemBuilder->items(); 0202 for (auto label : labels) { 0203 maxWidth = std::max(maxWidth, label->width()); 0204 maxHeight = std::max(maxHeight, label->height()); 0205 totalWidth += label->width(); 0206 totalHeight += label->height(); 0207 } 0208 0209 auto impWidth = isHorizontal() ? totalWidth : maxWidth; 0210 auto impHeight = isHorizontal() ? maxHeight : totalHeight; 0211 0212 if (qFuzzyCompare(impWidth, width()) && qFuzzyCompare(impHeight, height())) { 0213 return; 0214 } 0215 0216 setImplicitWidth(impWidth); 0217 setImplicitHeight(impHeight); 0218 0219 auto spacing = (isHorizontal() ? width() : height()) / (labels.size() - 1); 0220 auto i = 0; 0221 auto layoutWidth = isHorizontal() ? 0.0 : width(); 0222 auto layoutHeight = isHorizontal() ? height() : 0.0; 0223 0224 for (auto label : labels) { 0225 auto x = 0.0; 0226 auto y = 0.0; 0227 0228 switch (m_direction) { 0229 case Direction::HorizontalLeftRight: 0230 x = i * spacing; 0231 break; 0232 case Direction::HorizontalRightLeft: 0233 x = width() - i * spacing; 0234 break; 0235 case Direction::VerticalTopBottom: 0236 y = i * spacing; 0237 break; 0238 case Direction::VerticalBottomTop: 0239 y = height() - i * spacing; 0240 break; 0241 } 0242 0243 if (m_alignment & Qt::AlignHCenter) { 0244 x += (layoutWidth - label->width()) / 2; 0245 } else if (m_alignment & Qt::AlignRight) { 0246 x += layoutWidth - label->width(); 0247 } 0248 0249 if (m_alignment & Qt::AlignVCenter) { 0250 y += (layoutHeight - label->height()) / 2; 0251 } else if (m_alignment & Qt::AlignBottom) { 0252 y += layoutHeight - label->height(); 0253 } 0254 0255 if (m_constrainToBounds) { 0256 x = std::max(x, 0.0); 0257 x = x + label->width() > width() ? width() - label->width() : x; 0258 y = std::max(y, 0.0); 0259 y = y + label->height() > height() ? height() - label->height() : y; 0260 } 0261 0262 label->setX(x); 0263 label->setY(y); 0264 i++; 0265 } 0266 } 0267 0268 void AxisLabels::onBeginCreate(int index, QQuickItem *item) 0269 { 0270 QObject::connect(item, &QQuickItem::xChanged, this, [this]() { 0271 scheduleLayout(); 0272 }); 0273 QObject::connect(item, &QQuickItem::yChanged, this, [this]() { 0274 scheduleLayout(); 0275 }); 0276 QObject::connect(item, &QQuickItem::widthChanged, this, [this]() { 0277 scheduleLayout(); 0278 }); 0279 QObject::connect(item, &QQuickItem::heightChanged, this, [this]() { 0280 scheduleLayout(); 0281 }); 0282 0283 auto attached = static_cast<AxisLabelsAttached *>(qmlAttachedPropertiesObject<AxisLabels>(item, true)); 0284 attached->setIndex(index); 0285 attached->setLabel(m_source->item(index).toString()); 0286 } 0287 0288 #include "moc_AxisLabels.cpp"