1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Charts module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 or (at your option) any later version
20 ** approved by the KDE Free Qt Foundation. The licenses are as published by
21 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29 
30 #include <private/abstractchartlayout_p.h>
31 #include <private/chartpresenter_p.h>
32 #include <private/qlegend_p.h>
33 #include <private/chartaxiselement_p.h>
34 #include <private/charttitle_p.h>
35 #include <private/chartbackground_p.h>
36 #include <QtCore/QDebug>
37 
38 QT_CHARTS_BEGIN_NAMESPACE
39 
40 static const qreal golden_ratio = 0.4;
41 
AbstractChartLayout(ChartPresenter * presenter)42 AbstractChartLayout::AbstractChartLayout(ChartPresenter *presenter)
43     : m_presenter(presenter),
44       m_margins(20, 20, 20, 20)
45 {
46 }
47 
~AbstractChartLayout()48 AbstractChartLayout::~AbstractChartLayout()
49 {
50 }
51 
setGeometry(const QRectF & rect)52 void AbstractChartLayout::setGeometry(const QRectF &rect)
53 {
54     if (!rect.isValid())
55         return;
56     // If the chart has a fixed geometry then don't update visually
57     const bool updateLayout = (!m_presenter->isFixedGeometry() || m_presenter->geometry() == rect);
58     if (m_presenter->chart()->isVisible()) {
59         QList<ChartAxisElement *> axes = m_presenter->axisItems();
60         ChartTitle *title = m_presenter->titleElement();
61         QLegend *legend = m_presenter->legend();
62         ChartBackground *background = m_presenter->backgroundElement();
63 
64         QRectF contentGeometry = calculateBackgroundGeometry(rect, background, updateLayout);
65 
66         contentGeometry = calculateContentGeometry(contentGeometry);
67 
68         if (title && title->isVisible())
69             contentGeometry = calculateTitleGeometry(contentGeometry, title, updateLayout);
70 
71         if (legend->isAttachedToChart() && legend->isVisible())
72             contentGeometry = calculateLegendGeometry(contentGeometry, legend, updateLayout);
73 
74         contentGeometry = calculateAxisGeometry(contentGeometry, axes, updateLayout);
75 
76         m_presenter->setGeometry(contentGeometry);
77         if (updateLayout) {
78             if (m_presenter->chart()->chartType() == QChart::ChartTypeCartesian)
79                 static_cast<QGraphicsRectItem *>(m_presenter->plotAreaElement())->setRect(contentGeometry);
80             else
81                 static_cast<QGraphicsEllipseItem *>(m_presenter->plotAreaElement())->setRect(contentGeometry);
82         }
83     }
84 
85     QGraphicsLayout::setGeometry(rect);
86 }
87 
calculateContentGeometry(const QRectF & geometry) const88 QRectF AbstractChartLayout::calculateContentGeometry(const QRectF &geometry) const
89 {
90     return geometry.adjusted(m_margins.left(), m_margins.top(), -m_margins.right(), -m_margins.bottom());
91 }
92 
calculateContentMinimum(const QRectF & minimum) const93 QRectF AbstractChartLayout::calculateContentMinimum(const QRectF &minimum) const
94 {
95     return  minimum.adjusted(0, 0, m_margins.left() + m_margins.right(), m_margins.top() + m_margins.bottom());
96 }
97 
98 
calculateBackgroundGeometry(const QRectF & geometry,ChartBackground * background,bool update) const99 QRectF AbstractChartLayout::calculateBackgroundGeometry(const QRectF &geometry,
100                                                         ChartBackground *background,
101                                                         bool update) const
102 {
103     qreal left;
104     qreal top;
105     qreal right;
106     qreal bottom;
107     getContentsMargins(&left, &top, &right, &bottom);
108     QRectF backgroundGeometry = geometry.adjusted(left, top, -right, -bottom);
109     if (background && update)
110         background->setRect(backgroundGeometry);
111     return backgroundGeometry;
112 }
113 
calculateBackgroundMinimum(const QRectF & minimum) const114 QRectF AbstractChartLayout::calculateBackgroundMinimum(const QRectF &minimum) const
115 {
116     qreal left;
117     qreal top;
118     qreal right;
119     qreal bottom;
120     getContentsMargins(&left, &top, &right, &bottom);
121     return minimum.adjusted(0, 0, left + right, top + bottom);
122 }
123 
calculateLegendGeometry(const QRectF & geometry,QLegend * legend,bool update) const124 QRectF AbstractChartLayout::calculateLegendGeometry(const QRectF &geometry, QLegend *legend,
125                                                     bool update) const
126 {
127     QSizeF size = legend->effectiveSizeHint(Qt::PreferredSize, QSizeF(-1, -1));
128     QRectF legendRect;
129     QRectF result;
130 
131     switch (legend->alignment()) {
132     case Qt::AlignTop: {
133         legendRect = QRectF(geometry.topLeft(), QSizeF(geometry.width(), size.height()));
134         result = geometry.adjusted(0, legendRect.height(), 0, 0);
135         break;
136     }
137     case Qt::AlignBottom: {
138         legendRect = QRectF(QPointF(geometry.left(), geometry.bottom() - size.height()), QSizeF(geometry.width(), size.height()));
139         result = geometry.adjusted(0, 0, 0, -legendRect.height());
140         break;
141     }
142     case Qt::AlignLeft: {
143         qreal width = qMin(size.width(), geometry.width() * golden_ratio);
144         legendRect = QRectF(geometry.topLeft(), QSizeF(width, geometry.height()));
145         result = geometry.adjusted(width, 0, 0, 0);
146         break;
147     }
148     case Qt::AlignRight: {
149         qreal width = qMin(size.width(), geometry.width() * golden_ratio);
150         legendRect = QRectF(QPointF(geometry.right() - width, geometry.top()), QSizeF(width, geometry.height()));
151         result = geometry.adjusted(0, 0, -width, 0);
152         break;
153     }
154     default: {
155         legendRect = QRectF(0, 0, 0, 0);
156         result = geometry;
157         break;
158     }
159     }
160     if (update)
161         legend->setGeometry(legendRect);
162 
163     return result;
164 }
165 
calculateLegendMinimum(const QRectF & geometry,QLegend * legend) const166 QRectF AbstractChartLayout::calculateLegendMinimum(const QRectF &geometry, QLegend *legend) const
167 {
168     if (!legend->isAttachedToChart() || !legend->isVisible()) {
169         return geometry;
170     } else {
171         QSizeF minSize = legend->effectiveSizeHint(Qt::MinimumSize, QSizeF(-1, -1));
172         return geometry.adjusted(0, 0, minSize.width(), minSize.height());
173     }
174 }
175 
calculateTitleGeometry(const QRectF & geometry,ChartTitle * title,bool update) const176 QRectF AbstractChartLayout::calculateTitleGeometry(const QRectF &geometry, ChartTitle *title,
177                                                    bool update) const
178 {
179     if (update)
180         title->setGeometry(geometry);
181     if (title->text().isEmpty()) {
182         return geometry;
183     } else {
184         // Round to full pixel via QPoint to avoid one pixel clipping on the edge in some cases
185         QPointF center((geometry.center() - title->boundingRect().center()).toPoint());
186         if (update)
187             title->setPos(center.x(), title->pos().y());
188         return geometry.adjusted(0, title->boundingRect().height() + 1, 0, 0);
189     }
190 }
191 
calculateTitleMinimum(const QRectF & minimum,ChartTitle * title) const192 QRectF AbstractChartLayout::calculateTitleMinimum(const QRectF &minimum, ChartTitle *title) const
193 {
194     if (!title->isVisible() || title->text().isEmpty()) {
195         return minimum;
196     } else {
197         QSizeF min = title->sizeHint(Qt::MinimumSize);
198         return  minimum.adjusted(0, 0, min.width(), min.height());
199     }
200 }
201 
sizeHint(Qt::SizeHint which,const QSizeF & constraint) const202 QSizeF AbstractChartLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
203 {
204     Q_UNUSED(constraint);
205     if (which == Qt::MinimumSize) {
206         QList<ChartAxisElement *> axes = m_presenter->axisItems();
207         ChartTitle *title = m_presenter->titleElement();
208         QLegend *legend = m_presenter->legend();
209         QRectF minimumRect(0, 0, 0, 0);
210         minimumRect = calculateBackgroundMinimum(minimumRect);
211         minimumRect = calculateContentMinimum(minimumRect);
212         minimumRect = calculateTitleMinimum(minimumRect, title);
213         minimumRect = calculateLegendMinimum(minimumRect, legend);
214         minimumRect = calculateAxisMinimum(minimumRect, axes);
215         return  minimumRect.size().toSize();
216     }
217     return QSize(-1, -1);
218 }
219 
setMargins(const QMargins & margins)220 void AbstractChartLayout::setMargins(const QMargins &margins)
221 {
222     if (m_margins != margins) {
223         m_margins = margins;
224         updateGeometry();
225     }
226 }
227 
margins() const228 QMargins AbstractChartLayout::margins() const
229 {
230     return m_margins;
231 }
232 
233 QT_CHARTS_END_NAMESPACE
234