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"