1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <private/abstractchartlayout_p.h>
5#include <private/chartpresenter_p.h>
6#include <private/qlegend_p.h>
7#include <private/chartaxiselement_p.h>
8#include <private/charttitle_p.h>
9#include <private/chartbackground_p.h>
10#include <QtCore/QDebug>
11
12QT_BEGIN_NAMESPACE
13
14static const qreal golden_ratio = 0.4;
15
16AbstractChartLayout::AbstractChartLayout(ChartPresenter *presenter)
17 : m_presenter(presenter),
18 m_margins(20, 20, 20, 20)
19{
20}
21
22AbstractChartLayout::~AbstractChartLayout()
23{
24}
25
26void AbstractChartLayout::setGeometry(const QRectF &rect)
27{
28 if (!rect.isValid())
29 return;
30
31 // If the chart has a fixed geometry then don't update visually, unless plotbackground is
32 // visible.
33 const bool updateLayout = (!m_presenter->isFixedGeometry()
34 || m_presenter->isPlotAreaBackgroundVisible()
35 || m_presenter->geometry() == rect);
36 if (m_presenter->chart()->isVisible()) {
37 QList<ChartAxisElement *> axes = m_presenter->axisItems();
38 ChartTitle *title = m_presenter->titleElement();
39 QLegend *legend = m_presenter->legend();
40 ChartBackground *background = m_presenter->backgroundElement();
41
42 QRectF contentGeometry = calculateBackgroundGeometry(geometry: rect, background, update: updateLayout);
43
44 contentGeometry = calculateContentGeometry(geometry: contentGeometry);
45
46 if (title && title->isVisible())
47 contentGeometry = calculateTitleGeometry(geometry: contentGeometry, title, update: updateLayout);
48
49 if (legend->isAttachedToChart() && legend->isVisible())
50 contentGeometry = calculateLegendGeometry(geometry: contentGeometry, legend, update: updateLayout);
51
52 contentGeometry = calculateAxisGeometry(geometry: contentGeometry, axes, update: updateLayout);
53
54 if (contentGeometry.isValid()) {
55 m_presenter->setGeometry(contentGeometry);
56 if (updateLayout) {
57 if (m_presenter->chart()->chartType() == QChart::ChartTypeCartesian)
58 static_cast<QGraphicsRectItem *>(m_presenter->plotAreaElement())->setRect(contentGeometry);
59 else
60 static_cast<QGraphicsEllipseItem *>(m_presenter->plotAreaElement())->setRect(contentGeometry);
61 }
62 }
63 }
64
65 QGraphicsLayout::setGeometry(rect);
66}
67
68QRectF AbstractChartLayout::calculateContentGeometry(const QRectF &geometry) const
69{
70 return geometry.adjusted(xp1: m_margins.left(), yp1: m_margins.top(), xp2: -m_margins.right(), yp2: -m_margins.bottom());
71}
72
73QRectF AbstractChartLayout::calculateContentMinimum(const QRectF &minimum) const
74{
75 return minimum.adjusted(xp1: 0, yp1: 0, xp2: m_margins.left() + m_margins.right(), yp2: m_margins.top() + m_margins.bottom());
76}
77
78
79QRectF AbstractChartLayout::calculateBackgroundGeometry(const QRectF &geometry,
80 ChartBackground *background,
81 bool update) const
82{
83 qreal left;
84 qreal top;
85 qreal right;
86 qreal bottom;
87 getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom);
88 QRectF backgroundGeometry = geometry.adjusted(xp1: left, yp1: top, xp2: -right, yp2: -bottom);
89 if (background && update)
90 background->setRect(backgroundGeometry);
91 return backgroundGeometry;
92}
93
94QRectF AbstractChartLayout::calculateBackgroundMinimum(const QRectF &minimum) const
95{
96 qreal left;
97 qreal top;
98 qreal right;
99 qreal bottom;
100 getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom);
101 return minimum.adjusted(xp1: 0, yp1: 0, xp2: left + right, yp2: top + bottom);
102}
103
104QRectF AbstractChartLayout::calculateLegendGeometry(const QRectF &geometry, QLegend *legend,
105 bool update) const
106{
107 QSizeF size = legend->effectiveSizeHint(which: Qt::PreferredSize, constraint: QSizeF(-1, -1));
108 QRectF legendRect;
109 QRectF result;
110
111 switch (legend->alignment()) {
112 case Qt::AlignTop: {
113 legendRect = QRectF(geometry.topLeft(), QSizeF(geometry.width(), size.height()));
114 result = geometry.adjusted(xp1: 0, yp1: legendRect.height(), xp2: 0, yp2: 0);
115 break;
116 }
117 case Qt::AlignBottom: {
118 legendRect = QRectF(QPointF(geometry.left(), geometry.bottom() - size.height()), QSizeF(geometry.width(), size.height()));
119 result = geometry.adjusted(xp1: 0, yp1: 0, xp2: 0, yp2: -legendRect.height());
120 break;
121 }
122 case Qt::AlignLeft: {
123 qreal width = qMin(a: size.width(), b: geometry.width() * golden_ratio);
124 legendRect = QRectF(geometry.topLeft(), QSizeF(width, geometry.height()));
125 result = geometry.adjusted(xp1: width, yp1: 0, xp2: 0, yp2: 0);
126 break;
127 }
128 case Qt::AlignRight: {
129 qreal width = qMin(a: size.width(), b: geometry.width() * golden_ratio);
130 legendRect = QRectF(QPointF(geometry.right() - width, geometry.top()), QSizeF(width, geometry.height()));
131 result = geometry.adjusted(xp1: 0, yp1: 0, xp2: -width, yp2: 0);
132 break;
133 }
134 default: {
135 legendRect = QRectF(0, 0, 0, 0);
136 result = geometry;
137 break;
138 }
139 }
140 if (update)
141 legend->setGeometry(legendRect);
142
143 return result;
144}
145
146QRectF AbstractChartLayout::calculateLegendMinimum(const QRectF &geometry, QLegend *legend) const
147{
148 if (!legend->isAttachedToChart() || !legend->isVisible()) {
149 return geometry;
150 } else {
151 QSizeF minSize = legend->effectiveSizeHint(which: Qt::MinimumSize, constraint: QSizeF(-1, -1));
152 return geometry.adjusted(xp1: 0, yp1: 0, xp2: minSize.width(), yp2: minSize.height());
153 }
154}
155
156QRectF AbstractChartLayout::calculateTitleGeometry(const QRectF &geometry, ChartTitle *title,
157 bool update) const
158{
159 if (update)
160 title->setGeometry(geometry);
161 if (title->text().isEmpty()) {
162 return geometry;
163 } else {
164 // Round to full pixel via QPoint to avoid one pixel clipping on the edge in some cases
165 QPointF center((geometry.center() - title->boundingRect().center()).toPoint());
166 if (update)
167 title->setPos(ax: center.x(), ay: title->pos().y());
168 return geometry.adjusted(xp1: 0, yp1: title->boundingRect().height() + 1, xp2: 0, yp2: 0);
169 }
170}
171
172QRectF AbstractChartLayout::calculateTitleMinimum(const QRectF &minimum, ChartTitle *title) const
173{
174 if (!title->isVisible() || title->text().isEmpty()) {
175 return minimum;
176 } else {
177 QSizeF min = title->sizeHint(which: Qt::MinimumSize);
178 return minimum.adjusted(xp1: 0, yp1: 0, xp2: min.width(), yp2: min.height());
179 }
180}
181
182QSizeF AbstractChartLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
183{
184 Q_UNUSED(constraint);
185 if (which == Qt::MinimumSize) {
186 QList<ChartAxisElement *> axes = m_presenter->axisItems();
187 ChartTitle *title = m_presenter->titleElement();
188 QLegend *legend = m_presenter->legend();
189 QRectF minimumRect(0, 0, 0, 0);
190 minimumRect = calculateBackgroundMinimum(minimum: minimumRect);
191 minimumRect = calculateContentMinimum(minimum: minimumRect);
192 minimumRect = calculateTitleMinimum(minimum: minimumRect, title);
193 minimumRect = calculateLegendMinimum(geometry: minimumRect, legend);
194 minimumRect = calculateAxisMinimum(minimum: minimumRect, axes);
195 return minimumRect.size().toSize();
196 }
197 return QSize(-1, -1);
198}
199
200void AbstractChartLayout::setMargins(const QMargins &margins)
201{
202 if (m_margins != margins) {
203 m_margins = margins;
204 updateGeometry();
205 }
206}
207
208QMargins AbstractChartLayout::margins() const
209{
210 return m_margins;
211}
212
213QT_END_NAMESPACE
214

source code of qtcharts/src/charts/layout/abstractchartlayout.cpp