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