File indexing completed on 2024-04-28 15:29:33

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 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0152 void AxisLabels::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
0153 #else
0154 void AxisLabels::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
0155 #endif
0156 {
0157 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0158     QQuickItem::geometryChanged(newGeometry, oldGeometry);
0159 #else
0160     QQuickItem::geometryChange(newGeometry, oldGeometry);
0161 #endif
0162 
0163     if (newGeometry != oldGeometry) {
0164         scheduleLayout();
0165     }
0166 }
0167 
0168 void AxisLabels::scheduleLayout()
0169 {
0170     if (!m_layoutScheduled) {
0171         auto scheduleLayoutLambda = [this]() {
0172             layout();
0173             m_layoutScheduled = false;
0174         };
0175         QMetaObject::invokeMethod(this, scheduleLayoutLambda, Qt::QueuedConnection);
0176         m_layoutScheduled = true;
0177     }
0178 }
0179 
0180 bool AxisLabels::isHorizontal()
0181 {
0182     return m_direction == Direction::HorizontalLeftRight || m_direction == Direction::HorizontalRightLeft;
0183 }
0184 
0185 void AxisLabels::updateLabels()
0186 {
0187     m_itemBuilder->clear();
0188 
0189     if (!m_itemBuilder->component() || !m_source) {
0190         return;
0191     }
0192 
0193     m_itemBuilder->setCount(m_source->itemCount());
0194     m_itemBuilder->build(this);
0195 }
0196 
0197 void AxisLabels::layout()
0198 {
0199     if (!m_itemBuilder->isFinished()) {
0200         scheduleLayout();
0201         return;
0202     }
0203 
0204     auto maxWidth = 0.0;
0205     auto totalWidth = 0.0;
0206     auto maxHeight = 0.0;
0207     auto totalHeight = 0.0;
0208 
0209     auto labels = m_itemBuilder->items();
0210     for (auto label : labels) {
0211         maxWidth = std::max(maxWidth, label->width());
0212         maxHeight = std::max(maxHeight, label->height());
0213         totalWidth += label->width();
0214         totalHeight += label->height();
0215     }
0216 
0217     auto impWidth = isHorizontal() ? totalWidth : maxWidth;
0218     auto impHeight = isHorizontal() ? maxHeight : totalHeight;
0219 
0220     if (qFuzzyCompare(impWidth, width()) && qFuzzyCompare(impHeight, height())) {
0221         return;
0222     }
0223 
0224     setImplicitWidth(impWidth);
0225     setImplicitHeight(impHeight);
0226 
0227     auto spacing = (isHorizontal() ? width() : height()) / (labels.size() - 1);
0228     auto i = 0;
0229     auto layoutWidth = isHorizontal() ? 0.0 : width();
0230     auto layoutHeight = isHorizontal() ? height() : 0.0;
0231 
0232     for (auto label : labels) {
0233         auto x = 0.0;
0234         auto y = 0.0;
0235 
0236         switch (m_direction) {
0237         case Direction::HorizontalLeftRight:
0238             x = i * spacing;
0239             break;
0240         case Direction::HorizontalRightLeft:
0241             x = width() - i * spacing;
0242             break;
0243         case Direction::VerticalTopBottom:
0244             y = i * spacing;
0245             break;
0246         case Direction::VerticalBottomTop:
0247             y = height() - i * spacing;
0248             break;
0249         }
0250 
0251         if (m_alignment & Qt::AlignHCenter) {
0252             x += (layoutWidth - label->width()) / 2;
0253         } else if (m_alignment & Qt::AlignRight) {
0254             x += layoutWidth - label->width();
0255         }
0256 
0257         if (m_alignment & Qt::AlignVCenter) {
0258             y += (layoutHeight - label->height()) / 2;
0259         } else if (m_alignment & Qt::AlignBottom) {
0260             y += layoutHeight - label->height();
0261         }
0262 
0263         if (m_constrainToBounds) {
0264             x = std::max(x, 0.0);
0265             x = x + label->width() > width() ? width() - label->width() : x;
0266             y = std::max(y, 0.0);
0267             y = y + label->height() > height() ? height() - label->height() : y;
0268         }
0269 
0270         label->setX(x);
0271         label->setY(y);
0272         i++;
0273     }
0274 }
0275 
0276 void AxisLabels::onBeginCreate(int index, QQuickItem *item)
0277 {
0278     QObject::connect(item, &QQuickItem::xChanged, this, [this]() { scheduleLayout(); });
0279     QObject::connect(item, &QQuickItem::yChanged, this, [this]() { scheduleLayout(); });
0280     QObject::connect(item, &QQuickItem::widthChanged, this, [this]() { scheduleLayout(); });
0281     QObject::connect(item, &QQuickItem::heightChanged, this, [this]() { scheduleLayout(); });
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"