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 | |
42 | AbstractChartLayout::AbstractChartLayout(ChartPresenter *presenter) |
43 | : m_presenter(presenter), |
44 | m_margins(20, 20, 20, 20) |
45 | { |
46 | } |
47 | |
48 | AbstractChartLayout::~AbstractChartLayout() |
49 | { |
50 | } |
51 | |
52 | void AbstractChartLayout::setGeometry(const QRectF &rect) |
53 | { |
54 | if (!rect.isValid()) |
55 | return; |
56 | |
57 | // If the chart has a fixed geometry then don't update visually |
58 | const bool updateLayout = (!m_presenter->isFixedGeometry() || m_presenter->geometry() == rect); |
59 | if (m_presenter->chart()->isVisible()) { |
60 | QList<ChartAxisElement *> axes = m_presenter->axisItems(); |
61 | ChartTitle *title = m_presenter->titleElement(); |
62 | QLegend *legend = m_presenter->legend(); |
63 | ChartBackground *background = m_presenter->backgroundElement(); |
64 | |
65 | QRectF contentGeometry = calculateBackgroundGeometry(geometry: rect, background, update: updateLayout); |
66 | |
67 | contentGeometry = calculateContentGeometry(geometry: contentGeometry); |
68 | |
69 | if (title && title->isVisible()) |
70 | contentGeometry = calculateTitleGeometry(geometry: contentGeometry, title, update: updateLayout); |
71 | |
72 | if (legend->isAttachedToChart() && legend->isVisible()) |
73 | contentGeometry = calculateLegendGeometry(geometry: contentGeometry, legend, update: updateLayout); |
74 | |
75 | contentGeometry = calculateAxisGeometry(geometry: contentGeometry, axes, update: updateLayout); |
76 | |
77 | if (contentGeometry.isValid()) { |
78 | m_presenter->setGeometry(contentGeometry); |
79 | if (updateLayout) { |
80 | if (m_presenter->chart()->chartType() == QChart::ChartTypeCartesian) |
81 | static_cast<QGraphicsRectItem *>(m_presenter->plotAreaElement())->setRect(contentGeometry); |
82 | else |
83 | static_cast<QGraphicsEllipseItem *>(m_presenter->plotAreaElement())->setRect(contentGeometry); |
84 | } |
85 | } |
86 | } |
87 | |
88 | QGraphicsLayout::setGeometry(rect); |
89 | } |
90 | |
91 | QRectF AbstractChartLayout::calculateContentGeometry(const QRectF &geometry) const |
92 | { |
93 | return geometry.adjusted(xp1: m_margins.left(), yp1: m_margins.top(), xp2: -m_margins.right(), yp2: -m_margins.bottom()); |
94 | } |
95 | |
96 | QRectF AbstractChartLayout::calculateContentMinimum(const QRectF &minimum) const |
97 | { |
98 | return minimum.adjusted(xp1: 0, yp1: 0, xp2: m_margins.left() + m_margins.right(), yp2: m_margins.top() + m_margins.bottom()); |
99 | } |
100 | |
101 | |
102 | QRectF AbstractChartLayout::calculateBackgroundGeometry(const QRectF &geometry, |
103 | ChartBackground *background, |
104 | bool update) const |
105 | { |
106 | qreal left; |
107 | qreal top; |
108 | qreal right; |
109 | qreal bottom; |
110 | getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom); |
111 | QRectF backgroundGeometry = geometry.adjusted(xp1: left, yp1: top, xp2: -right, yp2: -bottom); |
112 | if (background && update) |
113 | background->setRect(backgroundGeometry); |
114 | return backgroundGeometry; |
115 | } |
116 | |
117 | QRectF AbstractChartLayout::calculateBackgroundMinimum(const QRectF &minimum) const |
118 | { |
119 | qreal left; |
120 | qreal top; |
121 | qreal right; |
122 | qreal bottom; |
123 | getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom); |
124 | return minimum.adjusted(xp1: 0, yp1: 0, xp2: left + right, yp2: top + bottom); |
125 | } |
126 | |
127 | QRectF AbstractChartLayout::calculateLegendGeometry(const QRectF &geometry, QLegend *legend, |
128 | bool update) const |
129 | { |
130 | QSizeF size = legend->effectiveSizeHint(which: Qt::PreferredSize, constraint: QSizeF(-1, -1)); |
131 | QRectF legendRect; |
132 | QRectF result; |
133 | |
134 | switch (legend->alignment()) { |
135 | case Qt::AlignTop: { |
136 | legendRect = QRectF(geometry.topLeft(), QSizeF(geometry.width(), size.height())); |
137 | result = geometry.adjusted(xp1: 0, yp1: legendRect.height(), xp2: 0, yp2: 0); |
138 | break; |
139 | } |
140 | case Qt::AlignBottom: { |
141 | legendRect = QRectF(QPointF(geometry.left(), geometry.bottom() - size.height()), QSizeF(geometry.width(), size.height())); |
142 | result = geometry.adjusted(xp1: 0, yp1: 0, xp2: 0, yp2: -legendRect.height()); |
143 | break; |
144 | } |
145 | case Qt::AlignLeft: { |
146 | qreal width = qMin(a: size.width(), b: geometry.width() * golden_ratio); |
147 | legendRect = QRectF(geometry.topLeft(), QSizeF(width, geometry.height())); |
148 | result = geometry.adjusted(xp1: width, yp1: 0, xp2: 0, yp2: 0); |
149 | break; |
150 | } |
151 | case Qt::AlignRight: { |
152 | qreal width = qMin(a: size.width(), b: geometry.width() * golden_ratio); |
153 | legendRect = QRectF(QPointF(geometry.right() - width, geometry.top()), QSizeF(width, geometry.height())); |
154 | result = geometry.adjusted(xp1: 0, yp1: 0, xp2: -width, yp2: 0); |
155 | break; |
156 | } |
157 | default: { |
158 | legendRect = QRectF(0, 0, 0, 0); |
159 | result = geometry; |
160 | break; |
161 | } |
162 | } |
163 | if (update) |
164 | legend->setGeometry(legendRect); |
165 | |
166 | return result; |
167 | } |
168 | |
169 | QRectF AbstractChartLayout::calculateLegendMinimum(const QRectF &geometry, QLegend *legend) const |
170 | { |
171 | if (!legend->isAttachedToChart() || !legend->isVisible()) { |
172 | return geometry; |
173 | } else { |
174 | QSizeF minSize = legend->effectiveSizeHint(which: Qt::MinimumSize, constraint: QSizeF(-1, -1)); |
175 | return geometry.adjusted(xp1: 0, yp1: 0, xp2: minSize.width(), yp2: minSize.height()); |
176 | } |
177 | } |
178 | |
179 | QRectF AbstractChartLayout::calculateTitleGeometry(const QRectF &geometry, ChartTitle *title, |
180 | bool update) const |
181 | { |
182 | if (update) |
183 | title->setGeometry(geometry); |
184 | if (title->text().isEmpty()) { |
185 | return geometry; |
186 | } else { |
187 | // Round to full pixel via QPoint to avoid one pixel clipping on the edge in some cases |
188 | QPointF center((geometry.center() - title->boundingRect().center()).toPoint()); |
189 | if (update) |
190 | title->setPos(ax: center.x(), ay: title->pos().y()); |
191 | return geometry.adjusted(xp1: 0, yp1: title->boundingRect().height() + 1, xp2: 0, yp2: 0); |
192 | } |
193 | } |
194 | |
195 | QRectF AbstractChartLayout::calculateTitleMinimum(const QRectF &minimum, ChartTitle *title) const |
196 | { |
197 | if (!title->isVisible() || title->text().isEmpty()) { |
198 | return minimum; |
199 | } else { |
200 | QSizeF min = title->sizeHint(which: Qt::MinimumSize); |
201 | return minimum.adjusted(xp1: 0, yp1: 0, xp2: min.width(), yp2: min.height()); |
202 | } |
203 | } |
204 | |
205 | QSizeF AbstractChartLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const |
206 | { |
207 | Q_UNUSED(constraint); |
208 | if (which == Qt::MinimumSize) { |
209 | QList<ChartAxisElement *> axes = m_presenter->axisItems(); |
210 | ChartTitle *title = m_presenter->titleElement(); |
211 | QLegend *legend = m_presenter->legend(); |
212 | QRectF minimumRect(0, 0, 0, 0); |
213 | minimumRect = calculateBackgroundMinimum(minimum: minimumRect); |
214 | minimumRect = calculateContentMinimum(minimum: minimumRect); |
215 | minimumRect = calculateTitleMinimum(minimum: minimumRect, title); |
216 | minimumRect = calculateLegendMinimum(geometry: minimumRect, legend); |
217 | minimumRect = calculateAxisMinimum(minimum: minimumRect, axes); |
218 | return minimumRect.size().toSize(); |
219 | } |
220 | return QSize(-1, -1); |
221 | } |
222 | |
223 | void AbstractChartLayout::setMargins(const QMargins &margins) |
224 | { |
225 | if (m_margins != margins) { |
226 | m_margins = margins; |
227 | updateGeometry(); |
228 | } |
229 | } |
230 | |
231 | QMargins AbstractChartLayout::margins() const |
232 | { |
233 | return m_margins; |
234 | } |
235 | |
236 | QT_CHARTS_END_NAMESPACE |
237 | |