1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <private/legendlayout_p.h>
5#include <private/chartpresenter_p.h>
6#include <private/qlegend_p.h>
7#include <private/abstractchartlayout_p.h>
8
9#include <private/qlegendmarker_p.h>
10#include <private/legendmarkeritem_p.h>
11#include <QtCharts/QLegendMarker>
12
13QT_BEGIN_NAMESPACE
14
15LegendLayout::LegendLayout(QLegend *legend)
16 : m_legend(legend),
17 m_offsetX(0),
18 m_offsetY(0)
19{
20
21}
22
23LegendLayout::~LegendLayout()
24{
25
26}
27
28void LegendLayout::setOffset(qreal x, qreal y)
29{
30 bool scrollHorizontal = true;
31 switch (m_legend->alignment()) {
32 case Qt::AlignTop:
33 case Qt::AlignBottom:
34 scrollHorizontal = true;
35 break;
36 case Qt::AlignLeft:
37 case Qt::AlignRight:
38 scrollHorizontal = false;
39 break;
40 }
41
42 // If detached, the scrolling direction is vertical instead of horizontal and vice versa.
43 if (!m_legend->isAttachedToChart())
44 scrollHorizontal = !scrollHorizontal;
45
46 QRectF boundingRect = geometry();
47 qreal left, top, right, bottom;
48 getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom);
49 boundingRect.adjust(xp1: left, yp1: top, xp2: -right, yp2: -bottom);
50
51 // Limit offset between m_minOffset and m_maxOffset
52 if (scrollHorizontal) {
53 if (m_width <= boundingRect.width())
54 return;
55
56 if (x != m_offsetX) {
57 m_offsetX = qBound(min: m_minOffsetX, val: x, max: m_maxOffsetX);
58 m_legend->d_ptr->items()->setPos(ax: -m_offsetX, ay: boundingRect.top());
59 }
60 } else {
61 if (m_height <= boundingRect.height())
62 return;
63
64 if (y != m_offsetY) {
65 m_offsetY = qBound(min: m_minOffsetY, val: y, max: m_maxOffsetY);
66 m_legend->d_ptr->items()->setPos(ax: boundingRect.left(), ay: -m_offsetY);
67 }
68 }
69}
70
71QPointF LegendLayout::offset() const
72{
73 return QPointF(m_offsetX, m_offsetY);
74}
75
76void LegendLayout::invalidate()
77{
78 QGraphicsLayout::invalidate();
79 if (m_legend->isAttachedToChart())
80 m_legend->d_ptr->m_presenter->layout()->invalidate();
81}
82
83void LegendLayout::setGeometry(const QRectF &rect)
84{
85 m_legend->d_ptr->items()->setVisible(m_legend->isVisible());
86
87 QGraphicsLayout::setGeometry(rect);
88
89 if (m_legend->isAttachedToChart())
90 setAttachedGeometry(rect);
91 else
92 setDettachedGeometry(rect);
93}
94
95void LegendLayout::setAttachedGeometry(const QRectF &rect)
96{
97 if (!rect.isValid())
98 return;
99
100 qreal oldOffsetX = m_offsetX;
101 qreal oldOffsetY = m_offsetY;
102 m_offsetX = 0;
103 m_offsetY = 0;
104
105 QSizeF size(0, 0);
106
107 if (m_legend->d_ptr->markers().isEmpty()) {
108 return;
109 }
110
111 m_width = 0;
112 m_height = 0;
113
114 qreal left, top, right, bottom;
115 getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom);
116
117 QRectF geometry = rect.adjusted(xp1: left, yp1: top, xp2: -right, yp2: -bottom);
118
119 switch(m_legend->alignment()) {
120 case Qt::AlignTop:
121 case Qt::AlignBottom: {
122 // Calculate the space required for items and add them to a sorted list.
123 qreal markerItemsWidth = 0;
124 qreal itemMargins = 0;
125 QList<LegendWidthStruct *> legendWidthList;
126 const auto markers = m_legend->d_ptr->markers();
127 for (QLegendMarker *marker : markers) {
128 LegendMarkerItem *item = marker->d_ptr->item();
129 if (item->isVisible()) {
130 QSizeF dummySize;
131 qreal itemWidth = item->sizeHint(which: Qt::PreferredSize, constraint: dummySize).width();
132 LegendWidthStruct *structItem = new LegendWidthStruct;
133 structItem->item = item;
134 structItem->width = itemWidth;
135 legendWidthList.append(t: structItem);
136 markerItemsWidth += itemWidth;
137 itemMargins += marker->d_ptr->item()->m_margin;
138 }
139 }
140 std::sort(first: legendWidthList.begin(), last: legendWidthList.end(), comp: widthLongerThan);
141
142 // If the items would occupy more space than is available, start truncating them
143 // from the longest one.
144 qreal availableGeometry = geometry.width() - itemMargins;
145 if (markerItemsWidth >= availableGeometry && legendWidthList.size() > 0) {
146 bool truncated(false);
147 int count = legendWidthList.size();
148 for (int i = 1; i < count; i++) {
149 int truncateIndex = i - 1;
150
151 while (legendWidthList.at(i: truncateIndex)->width >= legendWidthList.at(i)->width
152 && !truncated) {
153 legendWidthList.at(i: truncateIndex)->width--;
154 markerItemsWidth--;
155 if (i > 1) {
156 // Truncate the items that are before the truncated one in the list.
157 for (int j = truncateIndex - 1; j >= 0; j--) {
158 if (legendWidthList.at(i: truncateIndex)->width
159 < legendWidthList.at(i: j)->width) {
160 legendWidthList.at(i: j)->width--;
161 markerItemsWidth--;
162 }
163 }
164 }
165 if (markerItemsWidth < availableGeometry)
166 truncated = true;
167 }
168 // Truncate the last item if needed.
169 if (i == count - 1) {
170 if (legendWidthList.at(i: count - 1)->width
171 > legendWidthList.at(i: truncateIndex)->width) {
172 legendWidthList.at(i: count - 1)->width--;
173 markerItemsWidth--;
174 }
175 }
176
177 if (truncated)
178 break;
179 }
180 // Items are of same width and all of them need to be truncated
181 // or there is just one item that is truncated.
182 while (markerItemsWidth >= availableGeometry) {
183 for (int i = 0; i < count; i++) {
184 legendWidthList.at(i)->width--;
185 markerItemsWidth--;
186 }
187 }
188 }
189
190 QPointF point(0,0);
191
192 int markerCount = m_legend->d_ptr->markers().size();
193 for (int i = 0; i < markerCount; i++) {
194 QLegendMarker *marker;
195 if (m_legend->d_ptr->m_reverseMarkers)
196 marker = m_legend->d_ptr->markers().at(i: markerCount - 1 - i);
197 else
198 marker = m_legend->d_ptr->markers().at(i);
199 LegendMarkerItem *item = marker->d_ptr->item();
200 if (item->isVisible()) {
201 QRectF itemRect = geometry;
202 qreal availableWidth = 0;
203 for (int i = 0; i < legendWidthList.size(); ++i) {
204 if (legendWidthList.at(i)->item == item) {
205 availableWidth = legendWidthList.at(i)->width;
206 break;
207 }
208 }
209 itemRect.setWidth(availableWidth);
210 item->setGeometry(itemRect);
211 item->setPos(ax: point.x(),ay: geometry.height()/2 - item->boundingRect().height()/2);
212 const QRectF &rect = item->boundingRect();
213 size = size.expandedTo(otherSize: rect.size());
214 qreal w = rect.width();
215 m_width = m_width + w - item->m_margin;
216 point.setX(point.x() + w);
217 }
218 }
219 // Delete structs from the container
220 qDeleteAll(c: legendWidthList);
221
222 // Round to full pixel via QPoint to avoid one pixel clipping on the edge in some cases
223 if (m_width < geometry.width()) {
224 m_legend->d_ptr->items()->setPos(QPoint(geometry.width() / 2 - m_width / 2,
225 geometry.top()));
226 } else {
227 m_legend->d_ptr->items()->setPos(geometry.topLeft().toPoint());
228 }
229 m_height = size.height();
230 }
231 break;
232 case Qt::AlignLeft:
233 case Qt::AlignRight: {
234 QPointF point(0,0);
235 int markerCount = m_legend->d_ptr->markers().size();
236 for (int i = 0; i < markerCount; i++) {
237 QLegendMarker *marker;
238 if (m_legend->d_ptr->m_reverseMarkers)
239 marker = m_legend->d_ptr->markers().at(i: markerCount - 1 - i);
240 else
241 marker = m_legend->d_ptr->markers().at(i);
242 LegendMarkerItem *item = marker->d_ptr->item();
243 if (item->isVisible()) {
244 item->setGeometry(geometry);
245 item->setPos(point);
246 const QRectF &rect = item->boundingRect();
247 qreal h = rect.height();
248 size = size.expandedTo(otherSize: rect.size());
249 m_height+=h;
250 point.setY(point.y() + h);
251 }
252 }
253
254 // Round to full pixel via QPoint to avoid one pixel clipping on the edge in some cases
255 if (m_height < geometry.height()) {
256 m_legend->d_ptr->items()->setPos(QPoint(geometry.left(),
257 geometry.height() / 2 - m_height / 2));
258 } else {
259 m_legend->d_ptr->items()->setPos(geometry.topLeft().toPoint());
260 }
261 m_width = size.width();
262 break;
263 }
264 }
265
266 m_minOffsetX = -left;
267 m_minOffsetY = - top;
268 m_maxOffsetX = m_width - geometry.width() - right;
269 m_maxOffsetY = m_height - geometry.height() - bottom;
270
271 setOffset(x: oldOffsetX, y: oldOffsetY);
272}
273
274void LegendLayout::setDettachedGeometry(const QRectF &rect)
275{
276 if (!rect.isValid())
277 return;
278
279 // Detached layout is different.
280 // In detached mode legend may have multiple rows and columns, so layout calculations
281 // differ a log from attached mode.
282 // Also the scrolling logic is bit different.
283
284 qreal oldOffsetX = m_offsetX;
285 qreal oldOffsetY = m_offsetY;
286 m_offsetX = 0;
287 m_offsetY = 0;
288
289 qreal left, top, right, bottom;
290 getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom);
291 QRectF geometry = rect.adjusted(xp1: left, yp1: top, xp2: -right, yp2: -bottom);
292
293 QList<QLegendMarker *> markers = m_legend->d_ptr->markers();
294
295 if (markers.isEmpty())
296 return;
297
298 Qt::Alignment align = m_legend->alignment();
299 switch (align) {
300 case Qt::AlignTop:
301 case Qt::AlignBottom: {
302 QPointF point(0, (align == Qt::AlignTop) ? 0 : geometry.height());
303 m_width = 0;
304 m_height = 0;
305 for (int i = 0; i < markers.size(); i++) {
306 LegendMarkerItem *item = markers.at(i)->d_ptr->item();
307 if (item->isVisible()) {
308 item->setGeometry(geometry);
309 const QRectF &boundingRect = item->boundingRect();
310 qreal w = boundingRect.width();
311 qreal h = boundingRect.height();
312 m_width = qMax(a: m_width,b: w);
313 m_height = qMax(a: m_height,b: h);
314 item->setPos(ax: point.x(),ay: point.y() - ((align == Qt::AlignTop) ? 0 : h));
315 point.setX(point.x() + w);
316 if (point.x() + w > geometry.left() + geometry.width() - right) {
317 // Next item would go off rect.
318 point.setX(0);
319 point.setY(point.y() + ((align == Qt::AlignTop) ? h : -h));
320 if (i+1 < markers.size()) {
321 m_height += h;
322 }
323 }
324 }
325 }
326 m_legend->d_ptr->items()->setPos(geometry.topLeft());
327
328 m_minOffsetX = -left;
329 m_maxOffsetX = m_width - geometry.width() - right;
330 if (align == Qt::AlignTop) {
331 m_minOffsetY = -top;
332 m_maxOffsetY = m_height - geometry.height() - bottom;
333 } else {
334 m_minOffsetY = -m_height + geometry.height() - top;
335 m_maxOffsetY = -bottom;
336 }
337 }
338 break;
339 case Qt::AlignLeft:
340 case Qt::AlignRight: {
341 QPointF point((align == Qt::AlignLeft) ? 0 : geometry.width(), 0);
342 m_width = 0;
343 m_height = 0;
344 qreal maxWidth = 0;
345 for (int i = 0; i < markers.size(); i++) {
346 LegendMarkerItem *item = markers.at(i)->d_ptr->item();
347 if (item->isVisible()) {
348 item->setGeometry(geometry);
349 const QRectF &boundingRect = item->boundingRect();
350 qreal w = boundingRect.width();
351 qreal h = boundingRect.height();
352 m_height = qMax(a: m_height,b: h);
353 maxWidth = qMax(a: maxWidth,b: w);
354 item->setPos(ax: point.x() - ((align == Qt::AlignLeft) ? 0 : w),ay: point.y());
355 point.setY(point.y() + h);
356 if (point.y() + h > geometry.bottom()-bottom) {
357 // Next item would go off rect.
358 point.setX(point.x() + ((align == Qt::AlignLeft) ? maxWidth : -maxWidth));
359 point.setY(0);
360 if (i+1 < markers.size()) {
361 m_width += maxWidth;
362 maxWidth = 0;
363 }
364 }
365 }
366 }
367 m_width += maxWidth;
368 m_legend->d_ptr->items()->setPos(geometry.topLeft());
369
370 m_minOffsetY = -top;
371 m_maxOffsetY = m_height - geometry.height() - bottom;
372 if (align == Qt::AlignLeft) {
373 m_minOffsetX = -left;
374 m_maxOffsetX = m_width - geometry.width() - right;
375 } else {
376 m_minOffsetX = - m_width + geometry.width() - left;
377 m_maxOffsetX = - right;
378 }
379 }
380 break;
381 default:
382 break;
383 }
384
385 setOffset(x: oldOffsetX, y: oldOffsetY);
386}
387
388QSizeF LegendLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
389{
390 QSizeF size(0, 0);
391 qreal left, top, right, bottom;
392 getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom);
393
394 if(constraint.isValid()) {
395 const auto markers = m_legend->d_ptr->markers();
396 for (QLegendMarker *marker : markers) {
397 LegendMarkerItem *item = marker->d_ptr->item();
398 size = size.expandedTo(otherSize: item->effectiveSizeHint(which));
399 }
400 size = size.boundedTo(otherSize: constraint);
401 }
402 else if (constraint.width() >= 0) {
403 qreal width = 0;
404 qreal height = 0;
405 const auto markers = m_legend->d_ptr->markers();
406 for (QLegendMarker *marker : markers) {
407 LegendMarkerItem *item = marker->d_ptr->item();
408 width+=item->effectiveSizeHint(which).width();
409 height=qMax(a: height,b: item->effectiveSizeHint(which).height());
410 }
411
412 size = QSizeF(qMin(a: constraint.width(),b: width), height);
413 }
414 else if (constraint.height() >= 0) {
415 qreal width = 0;
416 qreal height = 0;
417 const auto markers = m_legend->d_ptr->markers();
418 for (QLegendMarker *marker : markers) {
419 LegendMarkerItem *item = marker->d_ptr->item();
420 width=qMax(a: width,b: item->effectiveSizeHint(which).width());
421 height+=height,item->effectiveSizeHint(which).height();
422 }
423 size = QSizeF(width,qMin(a: constraint.height(),b: height));
424 }
425 else {
426 const auto markers = m_legend->d_ptr->markers();
427 for (QLegendMarker *marker : markers) {
428 LegendMarkerItem *item = marker->d_ptr->item();
429 size = size.expandedTo(otherSize: item->effectiveSizeHint(which));
430 }
431 }
432 size += QSize(left + right, top + bottom);
433 return size;
434}
435
436bool LegendLayout::widthLongerThan(const LegendWidthStruct *item1,
437 const LegendWidthStruct *item2)
438{
439 return item1->width > item2->width;
440}
441
442QT_END_NAMESPACE
443

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtcharts/src/charts/legend/legendlayout.cpp