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

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