KChart

KChartChart.cpp
1/*
2 * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
3 *
4 * This file is part of the KD Chart library.
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include "KChartChart.h"
10#include "KChartChart_p.h"
11
12#include <QList>
13#include <QtDebug>
14#include <QGridLayout>
15#include <QLabel>
16#include <QHash>
17#include <QToolTip>
18#include <QPainter>
19#include <QPaintEvent>
20#include <QLayoutItem>
21#include <QPushButton>
22#include <QApplication>
23#include <QEvent>
24
25#include "KChartCartesianCoordinatePlane.h"
26#include "KChartAbstractCartesianDiagram.h"
27#include "KChartHeaderFooter.h"
28#include "KChartEnums.h"
29#include "KChartLegend.h"
30#include "KChartLayoutItems.h"
31#include <KChartTextAttributes.h>
32#include <KChartMarkerAttributes.h>
33#include "KChartPainterSaver_p.h"
34#include "KChartPrintingParameters.h"
35
36#include <algorithm>
37
38#if defined KDAB_EVAL
39#include "../evaldialog/evaldialog.h"
40#endif
41
42#if 0
43// dumpLayoutTree dumps a QLayout tree in a hopefully easy to read format to stderr - feel free to
44// use, improve and extend; it is very useful for looking at any layout problem.
45
46#include <typeinfo>
47
48// this is this different from both QRect::isEmpty() and QRect::isNull() for "wrong" QRects,
49// i.e. those where topLeft() is actually below and / or right of bottomRight().
50static bool isZeroArea(const QRect &r)
51{
52 return !r.width() || !r.height();
53}
54
55static QString lineProlog(int nestingDepth, int lineno)
56{
57 QString numbering(QString::number(lineno).rightJustified(5).append(QChar::fromAscii(':')));
58 QString indent(nestingDepth * 4, QLatin1Char(' '));
59 return numbering + indent;
60}
61
62static void dumpLayoutTreeRecurse(QLayout *l, int *counter, int depth)
63{
64 const QLatin1String colorOn(isZeroArea(l->geometry()) ? "\033[0m" : "\033[32m");
65 const QLatin1String colorOff("\033[0m");
66
67 QString prolog = lineProlog(depth, *counter);
68 (*counter)++;
69
70 qDebug() << colorOn + prolog << l->metaObject()->className() << l->geometry()
71 << "hint" << l->sizeHint()
72 << l->hasHeightForWidth() << "min" << l->minimumSize()
73 << "max" << l->maximumSize()
74 << l->expandingDirections() << l->alignment()
75 << colorOff;
76 for (int i = 0; i < l->count(); i++) {
77 QLayoutItem *child = l->itemAt(i);
78 if (QLayout *childL = child->layout()) {
79 dumpLayoutTreeRecurse(childL, counter, depth + 1);
80 } else {
81 // The isZeroArea check culls usually largely useless output - you might want to remove it in
82 // some debugging situations. Add a boolean parameter to this and dumpLayoutTree() if you do.
83 if (!isZeroArea(child->geometry())) {
84 prolog = lineProlog(depth + 1, *counter);
85 (*counter)++;
86 qDebug() << colorOn + prolog << typeid(*child).name() << child->geometry()
87 << "hint" << child->sizeHint()
88 << child->hasHeightForWidth() << "min" << child->minimumSize()
89 << "max" << child->maximumSize()
90 << child->expandingDirections() << child->alignment()
91 << colorOff;
92 }
93 }
94 }
95}
96
97static void dumpLayoutTree(QLayout *l)
98{
99 int counter = 0;
100 dumpLayoutTreeRecurse(l, &counter, 0);
101}
102#endif
103
104static const Qt::Alignment s_gridAlignments[ 3 ][ 3 ] = { // [ row ][ column ]
108};
109
110static void getRowAndColumnForPosition(KChartEnums::PositionValue pos, int* row, int* column)
111{
112 switch ( pos ) {
113 case KChartEnums::PositionNorthWest: *row = 0; *column = 0;
114 break;
115 case KChartEnums::PositionNorth: *row = 0; *column = 1;
116 break;
117 case KChartEnums::PositionNorthEast: *row = 0; *column = 2;
118 break;
119 case KChartEnums::PositionEast: *row = 1; *column = 2;
120 break;
121 case KChartEnums::PositionSouthEast: *row = 2; *column = 2;
122 break;
123 case KChartEnums::PositionSouth: *row = 2; *column = 1;
124 break;
125 case KChartEnums::PositionSouthWest: *row = 2; *column = 0;
126 break;
127 case KChartEnums::PositionWest: *row = 1; *column = 0;
128 break;
129 case KChartEnums::PositionCenter: *row = 1; *column = 1;
130 break;
131 default: *row = -1; *column = -1;
132 break;
133 }
134}
135
136using namespace KChart;
137
138// Layout widgets even if they are not visible (that's why isEmpty() is overridden) - at least that
139// was the original reason...
140class MyWidgetItem : public QWidgetItem
141{
142public:
143 explicit MyWidgetItem(QWidget *w, Qt::Alignment alignment = Qt::Alignment())
144 : QWidgetItem( w )
145 {
147 }
148
149 // All of the methods are reimplemented from QWidgetItem, and work around some oddity in QLayout and / or
150 // KD Chart - I forgot the details between writing this code as an experiment and committing it, very
151 // sorry about that!
152 // Feel free to comment out any of them and then try the line-breaking feature in horizontal legends in
153 // the Legends/Advanced example. It will not work well in various ways - won't get enough space and look
154 // very broken, will inhibit resizing the window etc.
155
156 QSize sizeHint() const override
157 {
158 QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
159 return w->sizeHint();
160 }
161
162 QSize minimumSize() const override
163 {
164 QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
165 return w->minimumSize();
166 }
167
168 QSize maximumSize() const override
169 {
170 // Not just passing on w->maximumSize() fixes that the size policy of Legend is disregarded, making
171 // Legend take all available space, which makes both the Legend internal layout and the overall
172 // layout of chart + legend look bad. QWidget::maximumSize() is not a virtual method, it's a
173 // property, so "overriding" that one would be even uglier, and prevent user-set property
174 // values from doing anything.
175 QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
176 QSize ret = w->maximumSize();
177 const QSize hint = w->sizeHint();
178 const QSizePolicy::Policy hPolicy = w->sizePolicy().horizontalPolicy();
179 if (hPolicy == QSizePolicy::Fixed || hPolicy == QSizePolicy::Maximum) {
180 ret.rwidth() = hint.width();
181 }
182 const QSizePolicy::Policy vPolicy = w->sizePolicy().verticalPolicy();
183 if (vPolicy == QSizePolicy::Fixed || vPolicy == QSizePolicy::Maximum) {
184 ret.rheight() = hint.height();
185 }
186 return ret;
187 }
188
189 Qt::Orientations expandingDirections() const override
190 {
191 QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
192 if ( isEmpty() ) {
193 return Qt::Orientations();
194 }
195 Qt::Orientations e = w->sizePolicy().expandingDirections();
196 return e;
197 }
198
199 void setGeometry(const QRect &g) override
200 {
201 QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
202 w->setGeometry(g);
203 }
204
205 QRect geometry() const override
206 {
207 QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
208 return w->geometry();
209 }
210
211 bool hasHeightForWidth() const override
212 {
213 QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
214 bool ret = !isEmpty() &&
215 qobject_cast< Legend* >( w )->hasHeightForWidth();
216 return ret;
217 }
218
219 int heightForWidth( int width ) const override
220 {
221 QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
222 int ret = w->heightForWidth( width );
223 return ret;
224 }
225
226 bool isEmpty() const override {
227 QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
228 // legend->hide() should indeed hide the legend,
229 // but a legend in a chart that hasn't been shown yet isn't hidden
230 // (as can happen when using Chart::paint() without showing the chart)
232 }
233};
234
235// When "abusing" QLayouts to lay out items with different geometry from the backing QWidgets,
236// some manual work is required to correctly update all the sublayouts.
237// This is because all the convenient ways to deal with QLayouts assume QWidgets somewhere.
238// What this does is somewhat similar to QLayout::activate(), but it never refers to the parent
239// QWidget which has the wrong geometry.
240static void invalidateLayoutTree( QLayoutItem *item )
241{
242 QLayout *layout = item->layout();
243 if ( layout ) {
244 const int count = layout->count();
245 for ( int i = 0; i < count; i++ ) {
246 invalidateLayoutTree( layout->itemAt( i ) );
247 }
248 }
249 item->invalidate();
250}
251
252void Chart::Private::slotUnregisterDestroyedLegend( Legend *l )
253{
254 chart->takeLegend( l );
255}
256
257void Chart::Private::slotUnregisterDestroyedHeaderFooter( HeaderFooter* hf )
258{
259 chart->takeHeaderFooter( hf );
260}
261
262void Chart::Private::slotUnregisterDestroyedPlane( AbstractCoordinatePlane* plane )
263{
264 coordinatePlanes.removeAll( plane );
265 for ( AbstractCoordinatePlane* p : std::as_const(coordinatePlanes) ) {
266 if ( p->referenceCoordinatePlane() == plane) {
267 p->setReferenceCoordinatePlane( nullptr );
268 }
269 }
270 plane->layoutPlanes();
271}
272
273Chart::Private::Private( Chart* chart_ )
274 : chart( chart_ )
275 , useNewLayoutSystem( false )
276 , layout(nullptr)
277 , vLayout(nullptr)
278 , planesLayout(nullptr)
279 , headerLayout(nullptr)
280 , footerLayout(nullptr)
281 , dataAndLegendLayout(nullptr)
282 , leftOuterSpacer(nullptr)
283 , rightOuterSpacer(nullptr)
284 , topOuterSpacer(nullptr)
285 , bottomOuterSpacer(nullptr)
286 , isFloatingLegendsLayoutDirty( true )
287 , isPlanesLayoutDirty( true )
288 , globalLeadingLeft(0)
289 , globalLeadingRight(0)
290 , globalLeadingTop(0)
291 , globalLeadingBottom(0)
292{
293 for ( int row = 0; row < 3; ++row ) {
294 for ( int column = 0; column < 3; ++column ) {
295 for ( int i = 0; i < 2; i++ ) {
296 innerHdFtLayouts[ i ][ row ][ column ] = nullptr;
297 }
298 }
299 }
300}
301
302Chart::Private::~Private()
303{
304}
305
306enum VisitorState{ Visited, Unknown };
307struct ConnectedComponentsComparator{
308 bool operator()( const LayoutGraphNode *lhs, const LayoutGraphNode *rhs ) const
309 {
310 return lhs->priority < rhs->priority;
311 }
312};
313
314static QVector< LayoutGraphNode* > getPrioritySortedConnectedComponents( QVector< LayoutGraphNode* > &nodeList )
315{
316 QVector< LayoutGraphNode* >connectedComponents;
318 for ( LayoutGraphNode* node : nodeList )
319 visitedComponents[ node ] = Unknown;
320 for ( int i = 0; i < nodeList.size(); ++i )
321 {
322 LayoutGraphNode *curNode = nodeList[ i ];
323 LayoutGraphNode *representativeNode = curNode;
324 if ( visitedComponents[ curNode ] != Visited )
325 {
327 stack.push( curNode );
328 while ( !stack.isEmpty() )
329 {
330 curNode = stack.pop();
331 Q_ASSERT( visitedComponents[ curNode ] != Visited );
332 visitedComponents[ curNode ] = Visited;
333 if ( curNode->bottomSuccesor && visitedComponents[ curNode->bottomSuccesor ] != Visited )
334 stack.push( curNode->bottomSuccesor );
335 if ( curNode->leftSuccesor && visitedComponents[ curNode->leftSuccesor ] != Visited )
336 stack.push( curNode->leftSuccesor );
337 if ( curNode->sharedSuccesor && visitedComponents[ curNode->sharedSuccesor ] != Visited )
338 stack.push( curNode->sharedSuccesor );
339 if ( curNode->priority < representativeNode->priority )
340 representativeNode = curNode;
341 }
342 connectedComponents.append( representativeNode );
343 }
344 }
345 std::sort( connectedComponents.begin(), connectedComponents.end(), ConnectedComponentsComparator() );
346 return connectedComponents;
347}
348
349struct PriorityComparator{
350public:
351 PriorityComparator( QHash< AbstractCoordinatePlane*, LayoutGraphNode* > mapping )
352 : m_mapping( mapping )
353 {}
354 bool operator() ( AbstractCoordinatePlane *lhs, AbstractCoordinatePlane *rhs ) const
355 {
356 const LayoutGraphNode *lhsNode = m_mapping[ lhs ];
357 Q_ASSERT( lhsNode );
358 const LayoutGraphNode *rhsNode = m_mapping[ rhs ];
359 Q_ASSERT( rhsNode );
360 return lhsNode->priority < rhsNode->priority;
361 }
362
364};
365
366void checkExistingAxes( LayoutGraphNode* node )
367{
368 if ( node && node->diagramPlane && node->diagramPlane->diagram() )
369 {
370 AbstractCartesianDiagram *diag = qobject_cast< AbstractCartesianDiagram* >( node->diagramPlane->diagram() );
371 if ( diag )
372 {
373 const CartesianAxisList axes = diag->axes();
374 for ( const CartesianAxis* axis : axes )
375 {
376 switch ( axis->position() )
377 {
378 case( CartesianAxis::Top ):
379 node->topAxesLayout = true;
380 break;
381 case( CartesianAxis::Bottom ):
382 node->bottomAxesLayout = true;
383 break;
384 case( CartesianAxis::Left ):
385 node->leftAxesLayout = true;
386 break;
387 case( CartesianAxis::Right ):
388 node->rightAxesLayout = true;
389 break;
390 }
391 }
392 }
393 }
394}
395
396static void mergeNodeAxisInformation( LayoutGraphNode* lhs, LayoutGraphNode* rhs )
397{
398 lhs->topAxesLayout |= rhs->topAxesLayout;
399 rhs->topAxesLayout = lhs->topAxesLayout;
400
401 lhs->bottomAxesLayout |= rhs->bottomAxesLayout;
402 rhs->bottomAxesLayout = lhs->bottomAxesLayout;
403
404 lhs->leftAxesLayout |= rhs->leftAxesLayout;
405 rhs->leftAxesLayout = lhs->leftAxesLayout;
406
407 lhs->rightAxesLayout |= rhs->rightAxesLayout;
408 rhs->rightAxesLayout = lhs->rightAxesLayout;
409}
410
411static CoordinatePlaneList findSharingAxisDiagrams( AbstractCoordinatePlane* plane,
412 const CoordinatePlaneList& list,
413 Chart::Private::AxisType type,
414 QVector< CartesianAxis* >* sharedAxes )
415{
416 if ( !plane || !plane->diagram() )
417 return CoordinatePlaneList();
418 Q_ASSERT( plane );
419 Q_ASSERT( plane->diagram() );
420 CoordinatePlaneList result;
421 AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* >( plane->diagram() );
422 if ( !diagram )
423 return CoordinatePlaneList();
424
426 for ( CartesianAxis* axis : diagram->axes() ) {
427 if ( ( type == Chart::Private::Ordinate &&
428 ( axis->position() == CartesianAxis::Left || axis->position() == CartesianAxis::Right ) )
429 ||
430 ( type == Chart::Private::Abscissa &&
431 ( axis->position() == CartesianAxis::Top || axis->position() == CartesianAxis::Bottom ) ) ) {
432 axes.append( axis );
433 }
434 }
435 for( AbstractCoordinatePlane *curPlane : list )
436 {
437 AbstractCartesianDiagram* diagram =
438 qobject_cast< AbstractCartesianDiagram* > ( curPlane->diagram() );
439 if ( !diagram )
440 continue;
441 for ( CartesianAxis* curSearchedAxis : std::as_const(axes) )
442 {
443 for( CartesianAxis* curAxis : diagram->axes() )
444 {
445 if ( curSearchedAxis == curAxis )
446 {
447 result.append( curPlane );
448 if ( !sharedAxes->contains( curSearchedAxis ) )
449 sharedAxes->append( curSearchedAxis );
450 }
451 }
452 }
453 }
454
455 return result;
456}
457
458/*
459 * this method determines the needed layout of the graph
460 * taking care of the sharing problematic
461 * its NOT allowed to have a diagram that shares
462 * more than one axis in the same direction
463 */
464QVector< LayoutGraphNode* > Chart::Private::buildPlaneLayoutGraph()
465{
468 // create all nodes and a mapping between plane and nodes
469 for ( AbstractCoordinatePlane* curPlane : std::as_const(coordinatePlanes) )
470 {
471 if ( curPlane->diagram() )
472 {
473 allNodes.append( new LayoutGraphNode );
474 allNodes[ allNodes.size() - 1 ]->diagramPlane = curPlane;
475 allNodes[ allNodes.size() - 1 ]->priority = allNodes.size();
476 checkExistingAxes( allNodes[ allNodes.size() - 1 ] );
477 planeNodeMapping[ curPlane ] = allNodes[ allNodes.size() - 1 ];
478 }
479 }
480 // build the graph connections
481 for ( LayoutGraphNode* curNode : std::as_const(allNodes) )
482 {
483 QVector< CartesianAxis* > sharedAxes;
484 CoordinatePlaneList xSharedPlanes = findSharingAxisDiagrams( curNode->diagramPlane, coordinatePlanes, Abscissa, &sharedAxes );
485 Q_ASSERT( sharedAxes.size() < 2 );
486 // TODO duplicated code make a method out of it
487 if ( sharedAxes.size() == 1 && xSharedPlanes.size() > 1 )
488 {
489 //xSharedPlanes.removeAll( sharedAxes.first()->diagram()->coordinatePlane() );
490 //std::sort( xSharedPlanes.begin(), xSharedPlanes.end(), PriorityComparator( planeNodeMapping ) );
491 for ( int i = 0; i < xSharedPlanes.size() - 1; ++i )
492 {
493 LayoutGraphNode *tmpNode = planeNodeMapping[ xSharedPlanes[ i ] ];
494 Q_ASSERT( tmpNode );
495 LayoutGraphNode *tmpNode2 = planeNodeMapping[ xSharedPlanes[ i + 1 ] ];
496 Q_ASSERT( tmpNode2 );
497 tmpNode->bottomSuccesor = tmpNode2;
498 }
499// if ( sharedAxes.first()->diagram() && sharedAxes.first()->diagram()->coordinatePlane() )
500// {
501// LayoutGraphNode *lastNode = planeNodeMapping[ xSharedPlanes.last() ];
502// Q_ASSERT( lastNode );
503// Q_ASSERT( sharedAxes.first()->diagram()->coordinatePlane() );
504// LayoutGraphNode *ownerNode = planeNodeMapping[ sharedAxes.first()->diagram()->coordinatePlane() ];
505// Q_ASSERT( ownerNode );
506// lastNode->bottomSuccesor = ownerNode;
507// }
508 //merge AxisInformation, needs a two pass run
509 LayoutGraphNode axisInfoNode;
510 for ( int count = 0; count < 2; ++count )
511 {
512 for ( int i = 0; i < xSharedPlanes.size(); ++i )
513 {
514 mergeNodeAxisInformation( &axisInfoNode, planeNodeMapping[ xSharedPlanes[ i ] ] );
515 }
516 }
517 }
518 sharedAxes.clear();
519 CoordinatePlaneList ySharedPlanes = findSharingAxisDiagrams( curNode->diagramPlane, coordinatePlanes, Ordinate, &sharedAxes );
520 Q_ASSERT( sharedAxes.size() < 2 );
521 if ( sharedAxes.size() == 1 && ySharedPlanes.size() > 1 )
522 {
523 //ySharedPlanes.removeAll( sharedAxes.first()->diagram()->coordinatePlane() );
524 //std::sort( ySharedPlanes.begin(), ySharedPlanes.end(), PriorityComparator( planeNodeMapping ) );
525 for ( int i = 0; i < ySharedPlanes.size() - 1; ++i )
526 {
527 LayoutGraphNode *tmpNode = planeNodeMapping[ ySharedPlanes[ i ] ];
528 Q_ASSERT( tmpNode );
529 LayoutGraphNode *tmpNode2 = planeNodeMapping[ ySharedPlanes[ i + 1 ] ];
530 Q_ASSERT( tmpNode2 );
531 tmpNode->leftSuccesor = tmpNode2;
532 }
533// if ( sharedAxes.first()->diagram() && sharedAxes.first()->diagram()->coordinatePlane() )
534// {
535// LayoutGraphNode *lastNode = planeNodeMapping[ ySharedPlanes.last() ];
536// Q_ASSERT( lastNode );
537// Q_ASSERT( sharedAxes.first()->diagram()->coordinatePlane() );
538// LayoutGraphNode *ownerNode = planeNodeMapping[ sharedAxes.first()->diagram()->coordinatePlane() ];
539// Q_ASSERT( ownerNode );
540// lastNode->bottomSuccesor = ownerNode;
541// }
542 //merge AxisInformation, needs a two pass run
543 LayoutGraphNode axisInfoNode;
544 for ( int count = 0; count < 2; ++count )
545 {
546 for ( int i = 0; i < ySharedPlanes.size(); ++i )
547 {
548 mergeNodeAxisInformation( &axisInfoNode, planeNodeMapping[ ySharedPlanes[ i ] ] );
549 }
550 }
551 }
552 sharedAxes.clear();
553 if ( curNode->diagramPlane->referenceCoordinatePlane() )
554 curNode->sharedSuccesor = planeNodeMapping[ curNode->diagramPlane->referenceCoordinatePlane() ];
555 }
556
557 return allNodes;
558}
559
560QHash<AbstractCoordinatePlane*, PlaneInfo> Chart::Private::buildPlaneLayoutInfos()
561{
562 /* There are two ways in which planes can be caused to interact in
563 * where they are put layouting wise: The first is the reference plane. If
564 * such a reference plane is set, on a plane, it will use the same cell in the
565 * layout as that one. In addition to this, planes can share an axis. In that case
566 * they will be laid out in relation to each other as suggested by the position
567 * of the axis. If, for example Plane1 and Plane2 share an axis at position Left,
568 * that will result in the layout: Axis Plane1 Plane 2, vertically. If Plane1
569 * also happens to be Plane2's reference plane, both planes are drawn over each
570 * other. The reference plane concept allows two planes to share the same space
571 * even if neither has any axis, and in case there are shared axis, it is used
572 * to decided, whether the planes should be painted on top of each other or
573 * laid out vertically or horizontally next to each other. */
576 for (AbstractCoordinatePlane* plane : std::as_const(coordinatePlanes) ) {
577 PlaneInfo p;
578 // first check if we share space with another plane
579 p.referencePlane = plane->referenceCoordinatePlane();
580 planeInfos.insert( plane, p );
581
582
583 const auto diagrams = plane->diagrams();
584 for ( AbstractDiagram* abstractDiagram : diagrams ) {
585 AbstractCartesianDiagram* diagram =
586 qobject_cast<AbstractCartesianDiagram*> ( abstractDiagram );
587 if ( !diagram ) {
588 continue;
589 }
590
591 const CartesianAxisList axes = diagram->axes();
592 for ( CartesianAxis* axis : axes ) {
593 if ( !axisInfos.contains( axis ) ) {
594 /* If this is the first time we see this axis, add it, with the
595 * current plane. The first plane added to the chart that has
596 * the axis associated with it thus "owns" it, and decides about
597 * layout. */
598 AxisInfo i;
599 i.plane = plane;
600 axisInfos.insert( axis, i );
601 } else {
602 AxisInfo i = axisInfos[axis];
603 if ( i.plane == plane ) {
604 continue; // we don't want duplicates, only shared
605 }
606
607 /* The user expects diagrams to be added on top, and to the right
608 * so that horizontally we need to move the new diagram, vertically
609 * the reference one. */
610 PlaneInfo pi = planeInfos[plane];
611 // plane-to-plane linking overrides linking via axes
612 if ( !pi.referencePlane ) {
613 // we're not the first plane to see this axis, mark us as a slave
614 pi.referencePlane = i.plane;
615 if ( axis->position() == CartesianAxis::Left ||
616 axis->position() == CartesianAxis::Right ) {
617 pi.horizontalOffset += 1;
618 }
619 planeInfos[plane] = pi;
620
621 pi = planeInfos[i.plane];
622 if ( axis->position() == CartesianAxis::Top ||
623 axis->position() == CartesianAxis::Bottom ) {
624 pi.verticalOffset += 1;
625 }
626
627 planeInfos[i.plane] = pi;
628 }
629 }
630 }
631 }
632 // Create a new grid layout for each plane that has no reference.
633 p = planeInfos[plane];
634 if ( p.referencePlane == nullptr ) {
635 p.gridLayout = new QGridLayout();
636 p.gridLayout->setContentsMargins( 0, 0, 0, 0 );
637 planeInfos[plane] = p;
638 }
639 }
640 return planeInfos;
641}
642
643void Chart::Private::slotLayoutPlanes()
644{
645 /*TODO make sure this is really needed */
646 const QBoxLayout::Direction oldPlanesDirection = planesLayout ? planesLayout->direction()
648 if ( planesLayout && dataAndLegendLayout )
649 dataAndLegendLayout->removeItem( planesLayout );
650
651 const bool hadPlanesLayout = planesLayout != nullptr;
652 int left, top, right, bottom;
653 if ( hadPlanesLayout )
654 planesLayout->getContentsMargins(&left, &top, &right, &bottom);
655
656 for ( AbstractLayoutItem* plane : std::as_const(planeLayoutItems) ) {
657 plane->removeFromParentLayout();
658 }
659 //TODO they should get a correct parent, but for now it works
660 for ( AbstractLayoutItem* plane : std::as_const(planeLayoutItems) ) {
661 if ( dynamic_cast< AutoSpacerLayoutItem* >( plane ) )
662 delete plane;
663 }
664
665 planeLayoutItems.clear();
666 delete planesLayout;
667 //hint: The direction is configurable by the user now, as
668 // we are using a QBoxLayout rather than a QVBoxLayout. (khz, 2007/04/25)
669 planesLayout = new QBoxLayout( oldPlanesDirection );
670
671 isPlanesLayoutDirty = true; // here we create the layouts; we need to "run" them before painting
672
673 if ( useNewLayoutSystem )
674 {
675 gridPlaneLayout = new QGridLayout;
676 planesLayout->addLayout( gridPlaneLayout );
677
678 if (hadPlanesLayout)
679 planesLayout->setContentsMargins(left, top, right, bottom);
680 planesLayout->setObjectName( QString::fromLatin1( "planesLayout" ) );
681
682 /* First go through all planes and all axes and figure out whether the planes
683 * need to coordinate. If they do, they share a grid layout, if not, each
684 * get their own. See buildPlaneLayoutInfos() for more details. */
685
686 QVector< LayoutGraphNode* > vals = buildPlaneLayoutGraph();
687 //qDebug() << Q_FUNC_INFO << "GraphNodes" << vals.size();
688 QVector< LayoutGraphNode* > connectedComponents = getPrioritySortedConnectedComponents( vals );
689 //qDebug() << Q_FUNC_INFO << "SubGraphs" << connectedComponents.size();
690 int row = 0;
691 int col = 0;
692 QSet< CartesianAxis* > laidOutAxes;
693 for ( int i = 0; i < connectedComponents.size(); ++i )
694 {
695 LayoutGraphNode *curComponent = connectedComponents[ i ];
696 for ( LayoutGraphNode *curRowComponent = curComponent; curRowComponent; curRowComponent = curRowComponent->bottomSuccesor )
697 {
698 col = 0;
699 for ( LayoutGraphNode *curColComponent = curRowComponent; curColComponent; curColComponent = curColComponent->leftSuccesor )
700 {
701 Q_ASSERT( curColComponent->diagramPlane->diagrams().size() == 1 );
702 const auto diags{curColComponent->diagramPlane->diagrams()};
703 for ( AbstractDiagram* diagram : diags )
704 {
705 const int planeRowOffset = 1;//curColComponent->topAxesLayout ? 1 : 0;
706 const int planeColOffset = 1;//curColComponent->leftAxesLayout ? 1 : 0;
707 //qDebug() << Q_FUNC_INFO << row << col << planeRowOffset << planeColOffset;
708
709 //qDebug() << Q_FUNC_INFO << row + planeRowOffset << col + planeColOffset;
710 planeLayoutItems << curColComponent->diagramPlane;
711 AbstractCartesianDiagram *cartDiag = qobject_cast< AbstractCartesianDiagram* >( diagram );
712 if ( cartDiag )
713 {
714 gridPlaneLayout->addItem( curColComponent->diagramPlane, row + planeRowOffset, col + planeColOffset, 2, 2 );
715 curColComponent->diagramPlane->setParentLayout( gridPlaneLayout );
716 QHBoxLayout *leftLayout = nullptr;
717 QHBoxLayout *rightLayout = nullptr;
718 QVBoxLayout *topLayout = nullptr;
719 QVBoxLayout *bottomLayout = nullptr;
720 if ( curComponent->sharedSuccesor )
721 {
722 gridPlaneLayout->addItem( curColComponent->sharedSuccesor->diagramPlane, row + planeRowOffset, col + planeColOffset, 2, 2 );
723 curColComponent->sharedSuccesor->diagramPlane->setParentLayout( gridPlaneLayout );
724 planeLayoutItems << curColComponent->sharedSuccesor->diagramPlane;
725 }
726 const auto axes = cartDiag->axes();
727 for ( CartesianAxis* axis : axes ) {
728 if ( axis->isAbscissa() )
729 {
730 if ( curColComponent->bottomSuccesor )
731 continue;
732 }
733 if ( laidOutAxes.contains( axis ) )
734 continue;
735 // if ( axis->diagram() != diagram )
736 // continue;
737 switch ( axis->position() )
738 {
739 case( CartesianAxis::Top ):
740 if ( !topLayout )
741 topLayout = new QVBoxLayout;
742 topLayout->addItem( axis );
743 axis->setParentLayout( topLayout );
744 break;
745 case( CartesianAxis::Bottom ):
746 if ( !bottomLayout )
747 bottomLayout = new QVBoxLayout;
748 bottomLayout->addItem( axis );
749 axis->setParentLayout( bottomLayout );
750 break;
751 case( CartesianAxis::Left ):
752 if ( !leftLayout )
753 leftLayout = new QHBoxLayout;
754 leftLayout->addItem( axis );
755 axis->setParentLayout( leftLayout );
756 break;
757 case( CartesianAxis::Right ):
758 if ( !rightLayout )
759 {
760 rightLayout = new QHBoxLayout;
761 }
762 rightLayout->addItem( axis );
763 axis->setParentLayout( rightLayout );
764 break;
765 }
766 planeLayoutItems << axis;
767 laidOutAxes.insert( axis );
768 }
769 if ( leftLayout )
770 gridPlaneLayout->addLayout( leftLayout, row + planeRowOffset, col, 2, 1,
772 if ( rightLayout )
773 gridPlaneLayout->addLayout( rightLayout, row, col + planeColOffset + 2, 2, 1,
775 if ( topLayout )
776 gridPlaneLayout->addLayout( topLayout, row, col + planeColOffset, 1, 2,
778 if ( bottomLayout )
779 gridPlaneLayout->addLayout( bottomLayout, row + planeRowOffset + 2,
780 col + planeColOffset, 1, 2, Qt::AlignTop | Qt::AlignHCenter );
781 }
782 else
783 {
784 gridPlaneLayout->addItem( curColComponent->diagramPlane, row, col, 4, 4 );
785 curColComponent->diagramPlane->setParentLayout( gridPlaneLayout );
786 }
787 col += planeColOffset + 2 + ( 1 );
788 }
789 }
790 int axisOffset = 2;//curRowComponent->topAxesLayout ? 1 : 0;
791 //axisOffset += curRowComponent->bottomAxesLayout ? 1 : 0;
792 const int rowOffset = axisOffset + 2;
793 row += rowOffset;
794 }
795
796 // if ( planesLayout->direction() == QBoxLayout::TopToBottom )
797 // ++row;
798 // else
799 // ++col;
800 }
801
802 qDeleteAll( vals );
803 // re-add our grid(s) to the chart's layout
804 if ( dataAndLegendLayout ) {
805 dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
806 dataAndLegendLayout->setRowStretch( 1, 1000 );
807 dataAndLegendLayout->setColumnStretch( 1, 1000 );
808 }
809 slotResizePlanes();
810#ifdef NEW_LAYOUT_DEBUG
811 for ( int i = 0; i < gridPlaneLayout->rowCount(); ++i )
812 {
813 for ( int j = 0; j < gridPlaneLayout->columnCount(); ++j )
814 {
815 if ( gridPlaneLayout->itemAtPosition( i, j ) )
816 qDebug() << Q_FUNC_INFO << "item at" << i << j << gridPlaneLayout->itemAtPosition( i, j )->geometry();
817 else
818 qDebug() << Q_FUNC_INFO << "item at" << i << j << "no item present";
819 }
820 }
821 //qDebug() << Q_FUNC_INFO << "Relayout ended";
822#endif
823 } else {
824 if ( hadPlanesLayout ) {
825 planesLayout->setContentsMargins( left, top, right, bottom );
826 }
827
828 planesLayout->setContentsMargins( 0, 0, 0, 0 );
829 planesLayout->setSpacing( 0 );
830 planesLayout->setObjectName( QString::fromLatin1( "planesLayout" ) );
831
832 /* First go through all planes and all axes and figure out whether the planes
833 * need to coordinate. If they do, they share a grid layout, if not, each
834 * gets their own. See buildPlaneLayoutInfos() for more details. */
835 QHash<AbstractCoordinatePlane*, PlaneInfo> planeInfos = buildPlaneLayoutInfos();
837 for ( AbstractCoordinatePlane* plane : std::as_const(coordinatePlanes) ) {
838 Q_ASSERT( planeInfos.contains(plane) );
839 PlaneInfo& pi = planeInfos[ plane ];
840 const int column = pi.horizontalOffset;
841 const int row = pi.verticalOffset;
842 //qDebug() << "processing plane at column" << column << "and row" << row;
843 QGridLayout *planeLayout = pi.gridLayout;
844
845 if ( !planeLayout ) {
846 PlaneInfo& refPi = pi;
847 // if this plane is sharing an axis with another one, recursively check for the original plane and use
848 // the grid of that as planeLayout.
849 while ( !planeLayout && refPi.referencePlane ) {
850 refPi = planeInfos[refPi.referencePlane];
851 planeLayout = refPi.gridLayout;
852 }
853 Q_ASSERT_X( planeLayout,
854 "Chart::Private::slotLayoutPlanes()",
855 "Invalid reference plane. Please check that the reference plane has been added to the Chart." );
856 } else {
857 planesLayout->addLayout( planeLayout );
858 }
859
860 /* Put the plane in the center of the layout. If this is our own, that's
861 * the middle of the layout, if we are sharing, it's a cell in the center
862 * column of the shared grid. */
863 planeLayoutItems << plane;
864 plane->setParentLayout( planeLayout );
865 planeLayout->addItem( plane, row, column, 1, 1 );
866 //qDebug() << "Chart slotLayoutPlanes() calls planeLayout->addItem("<< row << column << ")";
867 planeLayout->setRowStretch( row, 2 );
868 planeLayout->setColumnStretch( column, 2 );
869
870 const auto diagrams = plane->diagrams();
871 for ( AbstractDiagram* abstractDiagram : diagrams )
872 {
873 AbstractCartesianDiagram* diagram =
874 qobject_cast< AbstractCartesianDiagram* >( abstractDiagram );
875 if ( !diagram ) {
876 continue; // FIXME what about polar ?
877 }
878
879 if ( pi.referencePlane != nullptr )
880 {
881 pi.topAxesLayout = planeInfos[ pi.referencePlane ].topAxesLayout;
882 pi.bottomAxesLayout = planeInfos[ pi.referencePlane ].bottomAxesLayout;
883 pi.leftAxesLayout = planeInfos[ pi.referencePlane ].leftAxesLayout;
884 pi.rightAxesLayout = planeInfos[ pi.referencePlane ].rightAxesLayout;
885 }
886
887 // collect all axes of a kind into sublayouts
888 if ( pi.topAxesLayout == nullptr )
889 {
890 pi.topAxesLayout = new QVBoxLayout;
891 pi.topAxesLayout->setContentsMargins( 0, 0, 0, 0 );
892 pi.topAxesLayout->setObjectName( QString::fromLatin1( "topAxesLayout" ) );
893 }
894 if ( pi.bottomAxesLayout == nullptr )
895 {
896 pi.bottomAxesLayout = new QVBoxLayout;
897 pi.bottomAxesLayout->setContentsMargins( 0, 0, 0, 0 );
898 pi.bottomAxesLayout->setObjectName( QString::fromLatin1( "bottomAxesLayout" ) );
899 }
900 if ( pi.leftAxesLayout == nullptr )
901 {
902 pi.leftAxesLayout = new QHBoxLayout;
903 pi.leftAxesLayout->setContentsMargins( 0, 0, 0, 0 );
904 pi.leftAxesLayout->setObjectName( QString::fromLatin1( "leftAxesLayout" ) );
905 }
906 if ( pi.rightAxesLayout == nullptr )
907 {
908 pi.rightAxesLayout = new QHBoxLayout;
909 pi.rightAxesLayout->setContentsMargins( 0, 0, 0, 0 );
910 pi.rightAxesLayout->setObjectName( QString::fromLatin1( "rightAxesLayout" ) );
911 }
912
913 if ( pi.referencePlane != nullptr )
914 {
915 planeInfos[ pi.referencePlane ].topAxesLayout = pi.topAxesLayout;
916 planeInfos[ pi.referencePlane ].bottomAxesLayout = pi.bottomAxesLayout;
917 planeInfos[ pi.referencePlane ].leftAxesLayout = pi.leftAxesLayout;
918 planeInfos[ pi.referencePlane ].rightAxesLayout = pi.rightAxesLayout;
919 }
920
921 //pi.leftAxesLayout->setSizeConstraint( QLayout::SetFixedSize );
922 const CartesianAxisList axes = diagram->axes();
923 for ( CartesianAxis* axis : axes ) {
924 if ( axisInfos.contains( axis ) ) {
925 continue; // already laid out this one
926 }
927 Q_ASSERT ( axis );
928 axis->setCachedSizeDirty();
929 //qDebug() << "--------------- axis added to planeLayoutItems -----------------";
930 planeLayoutItems << axis;
931
932 switch ( axis->position() ) {
933 case CartesianAxis::Top:
934 axis->setParentLayout( pi.topAxesLayout );
935 pi.topAxesLayout->addItem( axis );
936 break;
937 case CartesianAxis::Bottom:
938 axis->setParentLayout( pi.bottomAxesLayout );
939 pi.bottomAxesLayout->addItem( axis );
940 break;
941 case CartesianAxis::Left:
942 axis->setParentLayout( pi.leftAxesLayout );
943 pi.leftAxesLayout->addItem( axis );
944 break;
945 case CartesianAxis::Right:
946 axis->setParentLayout( pi.rightAxesLayout );
947 pi.rightAxesLayout->addItem( axis );
948 break;
949 default:
950 Q_ASSERT_X( false, "Chart::paintEvent", "unknown axis position" );
951 break;
952 };
953 axisInfos.insert( axis, AxisInfo() );
954 }
955 /* Put each stack of axes-layouts in the cells surrounding the
956 * associated plane. We are laying out in the oder the planes
957 * were added, and the first one gets to lay out shared axes.
958 * Private axes go here as well, of course. */
959
960 if ( !pi.topAxesLayout->parent() ) {
961 planeLayout->addLayout( pi.topAxesLayout, row - 1, column );
962 }
963 if ( !pi.bottomAxesLayout->parent() ) {
964 planeLayout->addLayout( pi.bottomAxesLayout, row + 1, column );
965 }
966 if ( !pi.leftAxesLayout->parent() ) {
967 planeLayout->addLayout( pi.leftAxesLayout, row, column - 1 );
968 }
969 if ( !pi.rightAxesLayout->parent() ) {
970 planeLayout->addLayout( pi.rightAxesLayout,row, column + 1 );
971 }
972 }
973
974 // use up to four auto-spacer items in the corners around the diagrams:
975 #define ADD_AUTO_SPACER_IF_NEEDED( \
976 spacerRow, spacerColumn, hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ) \
977 { \
978 if ( hLayout || vLayout ) { \
979 AutoSpacerLayoutItem * spacer \
980 = new AutoSpacerLayoutItem( hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ); \
981 planeLayout->addItem( spacer, spacerRow, spacerColumn, 1, 1 ); \
982 spacer->setParentLayout( planeLayout ); \
983 planeLayoutItems << spacer; \
984 } \
985 }
986
987 if ( plane->isCornerSpacersEnabled() ) {
988 ADD_AUTO_SPACER_IF_NEEDED( row - 1, column - 1, false, pi.leftAxesLayout, false, pi.topAxesLayout )
989 ADD_AUTO_SPACER_IF_NEEDED( row + 1, column - 1, true, pi.leftAxesLayout, false, pi.bottomAxesLayout )
990 ADD_AUTO_SPACER_IF_NEEDED( row - 1, column + 1, false, pi.rightAxesLayout, true, pi.topAxesLayout )
991 ADD_AUTO_SPACER_IF_NEEDED( row + 1, column + 1, true, pi.rightAxesLayout, true, pi.bottomAxesLayout )
992 }
993 }
994 // re-add our grid(s) to the chart's layout
995 if ( dataAndLegendLayout ) {
996 dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
997 dataAndLegendLayout->setRowStretch( 1, 1000 );
998 dataAndLegendLayout->setColumnStretch( 1, 1000 );
999 }
1000
1001 slotResizePlanes();
1002 }
1003}
1004
1005void Chart::Private::createLayouts()
1006{
1007 // The toplevel layout provides the left and right global margins
1008 layout = new QHBoxLayout( chart );
1009 layout->setContentsMargins( 0, 0, 0, 0 );
1010 layout->setObjectName( QString::fromLatin1( "Chart::Private::layout" ) );
1011 layout->addSpacing( globalLeadingLeft );
1012 leftOuterSpacer = layout->itemAt( layout->count() - 1 )->spacerItem();
1013
1014 // The vLayout provides top and bottom global margins and lays
1015 // out headers, footers and the diagram area.
1016 vLayout = new QVBoxLayout();
1017 vLayout->setContentsMargins( 0, 0, 0, 0 );
1018 vLayout->setObjectName( QString::fromLatin1( "vLayout" ) );
1019
1020 layout->addLayout( vLayout, 1000 );
1021 layout->addSpacing( globalLeadingRight );
1022 rightOuterSpacer = layout->itemAt( layout->count() - 1 )->spacerItem();
1023
1024 // 1. the spacing above the header area
1025 vLayout->addSpacing( globalLeadingTop );
1026 topOuterSpacer = vLayout->itemAt( vLayout->count() - 1 )->spacerItem();
1027 // 2. the header area
1028 headerLayout = new QGridLayout();
1029 headerLayout->setContentsMargins( 0, 0, 0, 0 );
1030 vLayout->addLayout( headerLayout );
1031 // 3. the area containing coordinate planes, axes, and legends
1032 dataAndLegendLayout = new QGridLayout();
1033 dataAndLegendLayout->setContentsMargins( 0, 0, 0, 0 );
1034 dataAndLegendLayout->setObjectName( QString::fromLatin1( "dataAndLegendLayout" ) );
1035 vLayout->addLayout( dataAndLegendLayout, 1000 );
1036 // 4. the footer area
1037 footerLayout = new QGridLayout();
1038 footerLayout->setContentsMargins( 0, 0, 0, 0 );
1039 footerLayout->setObjectName( QString::fromLatin1( "footerLayout" ) );
1040 vLayout->addLayout( footerLayout );
1041
1042 // 5. Prepare the header / footer layout cells:
1043 // Each of the 9 header cells (the 9 footer cells)
1044 // contain their own QVBoxLayout
1045 // since there can be more than one header (footer) per cell.
1046 for ( int row = 0; row < 3; ++row ) {
1047 for ( int column = 0; column < 3; ++ column ) {
1048 const Qt::Alignment align = s_gridAlignments[ row ][ column ];
1049 for ( int headOrFoot = 0; headOrFoot < 2; headOrFoot++ ) {
1050 QVBoxLayout* innerLayout = new QVBoxLayout();
1051 innerLayout->setContentsMargins( 0, 0, 0, 0 );
1052 innerLayout->setAlignment( align );
1053 innerHdFtLayouts[ headOrFoot ][ row ][ column ] = innerLayout;
1054
1055 QGridLayout* outerLayout = headOrFoot == 0 ? headerLayout : footerLayout;
1056 outerLayout->addLayout( innerLayout, row, column, align );
1057 }
1058 }
1059 }
1060
1061 // 6. the spacing below the footer area
1062 vLayout->addSpacing( globalLeadingBottom );
1063 bottomOuterSpacer = vLayout->itemAt( vLayout->count() - 1 )->spacerItem();
1064
1065 // the data+axes area
1066 dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
1067 dataAndLegendLayout->setRowStretch( 1, 1 );
1068 dataAndLegendLayout->setColumnStretch( 1, 1 );
1069}
1070
1071void Chart::Private::slotResizePlanes()
1072{
1073 if ( !dataAndLegendLayout ) {
1074 return;
1075 }
1076 if ( !overrideSize.isValid() ) {
1077 // activate() takes the size from the layout's parent QWidget, which is not updated when overrideSize
1078 // is set. So don't let the layout grab the wrong size in that case.
1079 // When overrideSize *is* set, we call layout->setGeometry() in paint( QPainter*, const QRect& ),
1080 // which also "activates" the layout in the sense that it distributes space internally.
1081 layout->activate();
1082 }
1083 // Adapt diagram drawing to the new size
1084 for ( AbstractCoordinatePlane* plane : std::as_const(coordinatePlanes) ) {
1085 plane->layoutDiagrams();
1086 }
1087}
1088
1089void Chart::Private::updateDirtyLayouts()
1090{
1091 if ( isPlanesLayoutDirty ) {
1092 for ( AbstractCoordinatePlane* p : std::as_const(coordinatePlanes) ) {
1093 p->setGridNeedsRecalculate();
1094 p->layoutPlanes();
1095 p->layoutDiagrams();
1096 }
1097 }
1098 if ( isPlanesLayoutDirty || isFloatingLegendsLayoutDirty ) {
1099 chart->reLayoutFloatingLegends();
1100 }
1101 isPlanesLayoutDirty = false;
1102 isFloatingLegendsLayoutDirty = false;
1103}
1104
1105void Chart::Private::reapplyInternalLayouts()
1106{
1107 QRect geo = layout->geometry();
1108
1109 invalidateLayoutTree( layout );
1110 layout->setGeometry( geo );
1111 slotResizePlanes();
1112}
1113
1114void Chart::Private::paintAll( QPainter* painter )
1115{
1116 updateDirtyLayouts();
1117
1118 QRect rect( QPoint( 0, 0 ), overrideSize.isValid() ? overrideSize : chart->size() );
1119
1120 //qDebug() << this<<"::paintAll() uses layout size" << currentLayoutSize;
1121
1122 // Paint the background (if any)
1123 AbstractAreaBase::paintBackgroundAttributes( *painter, rect, backgroundAttributes );
1124 // Paint the frame (if any)
1125 AbstractAreaBase::paintFrameAttributes( *painter, rect, frameAttributes );
1126
1127 chart->reLayoutFloatingLegends();
1128
1129 for( AbstractLayoutItem* planeLayoutItem : std::as_const(planeLayoutItems) ) {
1130 planeLayoutItem->paintAll( *painter );
1131 }
1132 for( TextArea* textLayoutItem : std::as_const(textLayoutItems) ) {
1133 textLayoutItem->paintAll( *painter );
1134 }
1135 for ( Legend *legend : std::as_const(legends) ) {
1136 const bool hidden = legend->isHidden() && legend->testAttribute( Qt::WA_WState_ExplicitShowHide );
1137 if ( !hidden ) {
1138 //qDebug() << "painting legend at " << legend->geometry();
1139 legend->paintIntoRect( *painter, legend->geometry() );
1140 }
1141 }
1142}
1143
1144// ******** Chart interface implementation ***********
1145
1146#define d d_func()
1147
1148Chart::Chart ( QWidget* parent )
1149 : QWidget ( parent )
1150 , _d( new Private( this ) )
1151{
1152#if defined KDAB_EVAL
1153 EvalDialog::checkEvalLicense( "KD Chart" );
1154#endif
1155
1156 FrameAttributes frameAttrs;
1157// no frame per default...
1158// frameAttrs.setVisible( true );
1159 frameAttrs.setPen( QPen( Qt::black ) );
1160 frameAttrs.setPadding( 1 );
1161 setFrameAttributes( frameAttrs );
1162
1163 addCoordinatePlane( new CartesianCoordinatePlane ( this ) );
1164
1165 d->createLayouts();
1166}
1167
1168Chart::~Chart()
1169{
1170 // early disconnect the legend, otherwise destruction of a diagram might trigger an
1171 // update of the legend properties during destruction of the chart, leading to the
1172 // following assert in Qt6:
1173 // qobjectdefs_impl.h:119: ASSERT failure in KChart::Chart:
1174 // "Called object is not of the correct type (class destructor may have already run)"
1175 for (auto legend : d->legends) {
1176 disconnect(legend, nullptr, this, nullptr);
1177 }
1178
1179 delete d;
1180}
1181
1182void Chart::setFrameAttributes( const FrameAttributes &a )
1183{
1184 d->frameAttributes = a;
1185}
1186
1187FrameAttributes Chart::frameAttributes() const
1188{
1189 return d->frameAttributes;
1190}
1191
1192void Chart::setBackgroundAttributes( const BackgroundAttributes &a )
1193{
1194 d->backgroundAttributes = a;
1195}
1196
1197BackgroundAttributes Chart::backgroundAttributes() const
1198{
1199 return d->backgroundAttributes;
1200}
1201
1202//TODO KChart 3.0; change QLayout into QBoxLayout::Direction
1203void Chart::setCoordinatePlaneLayout( QLayout * layout )
1204{
1205 if (layout == d->planesLayout)
1206 return;
1207 if (d->planesLayout) {
1208 // detach all QLayoutItem's the previous planesLayout has cause
1209 // otherwise deleting the planesLayout would delete them too.
1210 for(int i = d->planesLayout->count() - 1; i >= 0; --i) {
1211 d->planesLayout->takeAt(i);
1212 }
1213 delete d->planesLayout;
1214 }
1215 d->planesLayout = qobject_cast<QBoxLayout*>( layout );
1216 d->slotLayoutPlanes();
1217}
1218
1219QLayout* Chart::coordinatePlaneLayout()
1220{
1221 return d->planesLayout;
1222}
1223
1224AbstractCoordinatePlane* Chart::coordinatePlane()
1225{
1226 if ( d->coordinatePlanes.isEmpty() ) {
1227 qWarning() << "Chart::coordinatePlane: warning: no coordinate plane defined.";
1228 return nullptr;
1229 } else {
1230 return d->coordinatePlanes.first();
1231 }
1232}
1233
1234CoordinatePlaneList Chart::coordinatePlanes()
1235{
1236 return d->coordinatePlanes;
1237}
1238
1239void Chart::addCoordinatePlane( AbstractCoordinatePlane* plane )
1240{
1241 // Append
1242 insertCoordinatePlane( d->coordinatePlanes.count(), plane );
1243}
1244
1245void Chart::insertCoordinatePlane( int index, AbstractCoordinatePlane* plane )
1246{
1247 if ( index < 0 || index > d->coordinatePlanes.count() ) {
1248 return;
1249 }
1250
1252 d, &Private::slotUnregisterDestroyedPlane );
1253 connect( plane, &AbstractCoordinatePlane::needUpdate, this, QOverload<>::of(&Chart::update) );
1254 connect( plane, &AbstractCoordinatePlane::needRelayout, d, &Private::slotResizePlanes ) ;
1255 connect( plane, &AbstractCoordinatePlane::needLayoutPlanes, d, &Private::slotLayoutPlanes ) ;
1256 connect( plane, &AbstractCoordinatePlane::propertiesChanged, this, &Chart::propertiesChanged );
1257 d->coordinatePlanes.insert( index, plane );
1258 plane->setParent( this );
1259 d->slotLayoutPlanes();
1260}
1261
1262void Chart::replaceCoordinatePlane( AbstractCoordinatePlane* plane,
1263 AbstractCoordinatePlane* oldPlane_ )
1264{
1265 if ( plane && oldPlane_ != plane ) {
1266 AbstractCoordinatePlane* oldPlane = oldPlane_;
1267 if ( d->coordinatePlanes.count() ) {
1268 if ( ! oldPlane ) {
1269 oldPlane = d->coordinatePlanes.first();
1270 if ( oldPlane == plane )
1271 return;
1272 }
1273 takeCoordinatePlane( oldPlane );
1274 }
1275 delete oldPlane;
1276 addCoordinatePlane( plane );
1277 }
1278}
1279
1280void Chart::takeCoordinatePlane( AbstractCoordinatePlane* plane )
1281{
1282 const int idx = d->coordinatePlanes.indexOf( plane );
1283 if ( idx != -1 ) {
1284 d->coordinatePlanes.takeAt( idx );
1285 disconnect( plane, nullptr, d, nullptr );
1286 disconnect( plane, nullptr, this, nullptr );
1287 plane->removeFromParentLayout();
1288 plane->setParent( nullptr );
1289 d->mouseClickedPlanes.removeAll(plane);
1290 }
1291 d->slotLayoutPlanes();
1292 // Need to emit the signal: In case somebody has connected the signal
1293 // to her own slot for e.g. calling update() on a widget containing the chart.
1294 Q_EMIT propertiesChanged();
1295}
1296
1297void Chart::setGlobalLeading( int left, int top, int right, int bottom )
1298{
1299 setGlobalLeadingLeft( left );
1300 setGlobalLeadingTop( top );
1301 setGlobalLeadingRight( right );
1302 setGlobalLeadingBottom( bottom );
1303}
1304
1305void Chart::setGlobalLeadingLeft( int leading )
1306{
1307 d->globalLeadingLeft = leading;
1308 d->leftOuterSpacer->changeSize( leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum );
1309 d->reapplyInternalLayouts();
1310}
1311
1312int Chart::globalLeadingLeft() const
1313{
1314 return d->globalLeadingLeft;
1315}
1316
1317void Chart::setGlobalLeadingTop( int leading )
1318{
1319 d->globalLeadingTop = leading;
1320 d->topOuterSpacer->changeSize( 0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed );
1321 d->reapplyInternalLayouts();
1322}
1323
1324int Chart::globalLeadingTop() const
1325{
1326 return d->globalLeadingTop;
1327}
1328
1329void Chart::setGlobalLeadingRight( int leading )
1330{
1331 d->globalLeadingRight = leading;
1332 d->rightOuterSpacer->changeSize( leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum );
1333 d->reapplyInternalLayouts();
1334}
1335
1336int Chart::globalLeadingRight() const
1337{
1338 return d->globalLeadingRight;
1339}
1340
1341void Chart::setGlobalLeadingBottom( int leading )
1342{
1343 d->globalLeadingBottom = leading;
1344 d->bottomOuterSpacer->changeSize( 0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed );
1345 d->reapplyInternalLayouts();
1346}
1347
1348int Chart::globalLeadingBottom() const
1349{
1350 return d->globalLeadingBottom;
1351}
1352
1353void Chart::paint( QPainter* painter, const QRect& rect )
1354{
1355 if ( rect.isEmpty() || !painter ) {
1356 return;
1357 }
1358
1361 int prevScaleFactor = PrintingParameters::scaleFactor();
1362
1363 PrintingParameters::setScaleFactor( qreal( painter->device()->logicalDpiX() ) / qreal( logicalDpiX() ) );
1364
1365 const QRect oldGeometry( geometry() );
1366 if ( oldGeometry != rect ) {
1367 setGeometry( rect );
1368 d->isPlanesLayoutDirty = true;
1369 d->isFloatingLegendsLayoutDirty = true;
1370 }
1371 painter->translate( rect.left(), rect.top() );
1372 d->paintAll( painter );
1373
1374 // for debugging
1375 // painter->setPen( QPen( Qt::blue, 8 ) );
1376 // painter->drawRect( rect );
1377
1378 painter->translate( -rect.left(), -rect.top() );
1379 if ( oldGeometry != rect ) {
1380 setGeometry( oldGeometry );
1381 d->isPlanesLayoutDirty = true;
1382 d->isFloatingLegendsLayoutDirty = true;
1383 }
1384
1385 PrintingParameters::setScaleFactor( prevScaleFactor );
1387}
1388
1389void Chart::resizeEvent ( QResizeEvent* event )
1390{
1391 d->isPlanesLayoutDirty = true;
1392 d->isFloatingLegendsLayoutDirty = true;
1394}
1395
1396void Chart::reLayoutFloatingLegends()
1397{
1398 for( Legend *legend : std::as_const(d->legends) ) {
1399 const bool hidden = legend->isHidden() && legend->testAttribute( Qt::WA_WState_ExplicitShowHide );
1400 if ( legend->position().isFloating() && !hidden ) {
1401 // resize the legend
1402 const QSize legendSize( legend->sizeHint() );
1403 legend->setGeometry( QRect( legend->geometry().topLeft(), legendSize ) );
1404 // find the legends corner point (reference point plus any paddings)
1405 const RelativePosition relPos( legend->floatingPosition() );
1406 QPointF pt( relPos.calculatedPoint( size() ) );
1407 //qDebug() << pt;
1408 // calculate the legend's top left point
1409 const Qt::Alignment alignTopLeft = Qt::AlignBottom | Qt::AlignLeft;
1410 if ( (relPos.alignment() & alignTopLeft) != alignTopLeft ) {
1411 if ( relPos.alignment() & Qt::AlignRight )
1412 pt.rx() -= legendSize.width();
1413 else if ( relPos.alignment() & Qt::AlignHCenter )
1414 pt.rx() -= 0.5 * legendSize.width();
1415
1416 if ( relPos.alignment() & Qt::AlignBottom )
1417 pt.ry() -= legendSize.height();
1418 else if ( relPos.alignment() & Qt::AlignVCenter )
1419 pt.ry() -= 0.5 * legendSize.height();
1420 }
1421 //qDebug() << pt << endl;
1422 legend->move( static_cast<int>(pt.x()), static_cast<int>(pt.y()) );
1423 }
1424 }
1425}
1426
1427
1428void Chart::paintEvent( QPaintEvent* )
1429{
1430 QPainter painter( this );
1431 d->paintAll( &painter );
1432 Q_EMIT finishedDrawing();
1433}
1434
1435void Chart::addHeaderFooter( HeaderFooter* hf )
1436{
1437 Q_ASSERT( hf->type() == HeaderFooter::Header || hf->type() == HeaderFooter::Footer );
1438 int row;
1439 int column;
1440 getRowAndColumnForPosition( hf->position().value(), &row, &column );
1441 if ( row == -1 ) {
1442 qWarning( "Unknown header/footer position" );
1443 return;
1444 }
1445
1446 d->headerFooters.append( hf );
1447 d->textLayoutItems.append( hf );
1448 connect( hf, &HeaderFooter::destroyedHeaderFooter,
1449 d, &Private::slotUnregisterDestroyedHeaderFooter );
1450 connect( hf, &HeaderFooter::positionChanged,
1451 d, &Private::slotHeaderFooterPositionChanged );
1452
1453 // set the text attributes (why?)
1454
1455 TextAttributes textAttrs( hf->textAttributes() );
1456 Measure measure( textAttrs.fontSize() );
1457 measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum );
1458 measure.setValue( 20 );
1459 textAttrs.setFontSize( measure );
1460 hf->setTextAttributes( textAttrs );
1461
1462 // add it to the appropriate layout
1463
1464 int innerLayoutIdx = hf->type() == HeaderFooter::Header ? 0 : 1;
1465 QVBoxLayout* headerFooterLayout = d->innerHdFtLayouts[ innerLayoutIdx ][ row ][ column ];
1466
1467 hf->setParentLayout( headerFooterLayout );
1468 hf->setAlignment( s_gridAlignments[ row ][ column ] );
1469 headerFooterLayout->addItem( hf );
1470
1471 d->slotResizePlanes();
1472}
1473
1474void Chart::replaceHeaderFooter( HeaderFooter* headerFooter,
1475 HeaderFooter* oldHeaderFooter_ )
1476{
1477 if ( headerFooter && oldHeaderFooter_ != headerFooter ) {
1478 HeaderFooter* oldHeaderFooter = oldHeaderFooter_;
1479 if ( d->headerFooters.count() ) {
1480 if ( ! oldHeaderFooter ) {
1481 oldHeaderFooter = d->headerFooters.first();
1482 if ( oldHeaderFooter == headerFooter )
1483 return;
1484 }
1485 takeHeaderFooter( oldHeaderFooter );
1486 }
1487 delete oldHeaderFooter;
1488 addHeaderFooter( headerFooter );
1489 }
1490}
1491
1492void Chart::takeHeaderFooter( HeaderFooter* headerFooter )
1493{
1494 const int idx = d->headerFooters.indexOf( headerFooter );
1495 if ( idx == -1 ) {
1496 return;
1497 }
1498 disconnect( headerFooter, &HeaderFooter::destroyedHeaderFooter,
1499 d, &Private::slotUnregisterDestroyedHeaderFooter );
1500
1501 d->headerFooters.takeAt( idx );
1502 headerFooter->removeFromParentLayout();
1503 headerFooter->setParentLayout( nullptr );
1504 d->textLayoutItems.remove( d->textLayoutItems.indexOf( headerFooter ) );
1505
1506 d->slotResizePlanes();
1507}
1508
1509void Chart::Private::slotHeaderFooterPositionChanged( HeaderFooter* hf )
1510{
1511 chart->takeHeaderFooter( hf );
1512 chart->addHeaderFooter( hf );
1513}
1514
1515HeaderFooter* Chart::headerFooter()
1516{
1517 if ( d->headerFooters.isEmpty() ) {
1518 return nullptr;
1519 } else {
1520 return d->headerFooters.first();
1521 }
1522}
1523
1524HeaderFooterList Chart::headerFooters()
1525{
1526 return d->headerFooters;
1527}
1528
1529void Chart::Private::slotLegendPositionChanged( AbstractAreaWidget* aw )
1530{
1531 Legend* legend = qobject_cast< Legend* >( aw );
1532 Q_ASSERT( legend );
1533 chart->takeLegend( legend );
1534 chart->addLegendInternal( legend, false );
1535}
1536
1537void Chart::addLegend( Legend* legend )
1538{
1539 legend->show();
1540 addLegendInternal( legend, true );
1541 Q_EMIT propertiesChanged();
1542}
1543
1544void Chart::addLegendInternal( Legend* legend, bool setMeasures )
1545{
1546 if ( !legend ) {
1547 return;
1548 }
1549
1550 KChartEnums::PositionValue pos = legend->position().value();
1551 if ( pos == KChartEnums::PositionCenter ) {
1552 qWarning( "Not showing legend because PositionCenter is not supported for legends." );
1553 }
1554
1555 int row;
1556 int column;
1557 getRowAndColumnForPosition( pos, &row, &column );
1558 if ( row < 0 && pos != KChartEnums::PositionFloating ) {
1559 qWarning( "Not showing legend because of unknown legend position." );
1560 return;
1561 }
1562
1563 d->legends.append( legend );
1564 legend->setParent( this );
1565
1566 // set text attributes (why?)
1567
1568 if ( setMeasures ) {
1569 TextAttributes textAttrs( legend->textAttributes() );
1570 Measure measure( textAttrs.fontSize() );
1571 measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum );
1572 measure.setValue( 20 );
1573 textAttrs.setFontSize( measure );
1574 legend->setTextAttributes( textAttrs );
1575
1576 textAttrs = legend->titleTextAttributes();
1577 measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum );
1578 measure.setValue( 24 );
1579 textAttrs.setFontSize( measure );
1580
1581 legend->setTitleTextAttributes( textAttrs );
1582 legend->setReferenceArea( this );
1583 }
1584
1585 // add it to the appropriate layout
1586
1587 if ( pos != KChartEnums::PositionFloating ) {
1588 legend->needSizeHint();
1589
1590 // in each edge and corner of the outer layout, there's a grid for the different alignments that we create
1591 // on demand. we don't remove it when empty.
1592
1593 QLayoutItem* edgeItem = d->dataAndLegendLayout->itemAtPosition( row, column );
1594 QGridLayout* alignmentsLayout = dynamic_cast< QGridLayout* >( edgeItem );
1595 Q_ASSERT( !edgeItem || alignmentsLayout ); // if it exists, it must be a QGridLayout
1596 if ( !alignmentsLayout ) {
1597 alignmentsLayout = new QGridLayout;
1598 d->dataAndLegendLayout->addLayout( alignmentsLayout, row, column );
1599 alignmentsLayout->setContentsMargins( 0, 0, 0, 0 );
1600 }
1601
1602 // in case there are several legends in the same edge or corner with the same alignment, they are stacked
1603 // vertically using a QVBoxLayout. it is created on demand as above.
1604
1605 row = 1;
1606 column = 1;
1607 for ( int i = 0; i < 3; i++ ) {
1608 for ( int j = 0; j < 3; j++ ) {
1609 Qt::Alignment align = s_gridAlignments[ i ][ j ];
1610 if ( align == legend->alignment() ) {
1611 row = i;
1612 column = j;
1613 break;
1614 }
1615 }
1616 }
1617
1618 QLayoutItem* alignmentItem = alignmentsLayout->itemAtPosition( row, column );
1619 QVBoxLayout* sameAlignmentLayout = dynamic_cast< QVBoxLayout* >( alignmentItem );
1620 Q_ASSERT( !alignmentItem || sameAlignmentLayout ); // if it exists, it must be a QVBoxLayout
1621 if ( !sameAlignmentLayout ) {
1622 sameAlignmentLayout = new QVBoxLayout;
1623 alignmentsLayout->addLayout( sameAlignmentLayout, row, column );
1624 sameAlignmentLayout->setContentsMargins( 0, 0, 0, 0 );
1625 }
1626
1627 sameAlignmentLayout->addItem( new MyWidgetItem( legend, legend->alignment() ) );
1628 }
1629
1630 connect( legend, &Legend::destroyedLegend,
1631 d, &Private::slotUnregisterDestroyedLegend );
1632 connect( legend, &Legend::positionChanged,
1633 d, &Private::slotLegendPositionChanged );
1634 connect( legend, &Legend::propertiesChanged, this, &Chart::propertiesChanged );
1635
1636 d->slotResizePlanes();
1637}
1638
1639void Chart::replaceLegend( Legend* legend, Legend* oldLegend_ )
1640{
1641 if ( legend && oldLegend_ != legend ) {
1642 Legend* oldLegend = oldLegend_;
1643 if ( d->legends.count() ) {
1644 if ( ! oldLegend ) {
1645 oldLegend = d->legends.first();
1646 if ( oldLegend == legend )
1647 return;
1648 }
1649 takeLegend( oldLegend );
1650 }
1651 delete oldLegend;
1652 addLegend( legend );
1653 }
1654}
1655
1656void Chart::takeLegend( Legend* legend )
1657{
1658 const int idx = d->legends.indexOf( legend );
1659 if ( idx == -1 ) {
1660 return;
1661 }
1662
1663 d->legends.takeAt( idx );
1664 disconnect( legend, nullptr, d, nullptr );
1665 disconnect( legend, nullptr, this, nullptr );
1666 // the following removes the legend from its layout and destroys its MyWidgetItem (the link to the layout)
1667 legend->setParent( nullptr );
1668
1669 d->slotResizePlanes();
1670 Q_EMIT propertiesChanged();
1671}
1672
1673Legend* Chart::legend()
1674{
1675 return d->legends.isEmpty() ? nullptr : d->legends.first();
1676}
1677
1678LegendList Chart::legends()
1679{
1680 return d->legends;
1681}
1682
1684{
1685 const QPoint pos = mapFromGlobal( event->globalPos() );
1686
1687 for( AbstractCoordinatePlane* plane : std::as_const(d->coordinatePlanes) ) {
1688 if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
1689 QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(),
1690 event->button(), event->buttons(), event->modifiers() );
1691 plane->mousePressEvent( &ev );
1692 d->mouseClickedPlanes.append( plane );
1693 }
1694 }
1695}
1696
1698{
1699 const QPoint pos = mapFromGlobal( event->globalPos() );
1700
1701 for( AbstractCoordinatePlane* plane : std::as_const(d->coordinatePlanes) ) {
1702 if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
1703 QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(),
1704 event->button(), event->buttons(), event->modifiers() );
1705 plane->mouseDoubleClickEvent( &ev );
1706 }
1707 }
1708}
1709
1711{
1712 auto eventReceivers = QSet<AbstractCoordinatePlane*>(d->mouseClickedPlanes.cbegin(), d->mouseClickedPlanes.cend());
1713
1714 for( AbstractCoordinatePlane* plane : std::as_const(d->coordinatePlanes) ) {
1715 if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
1716 eventReceivers.insert( plane );
1717 }
1718 }
1719
1720 const QPoint pos = mapFromGlobal( event->globalPos() );
1721
1722 for( AbstractCoordinatePlane* plane : std::as_const(eventReceivers) ) {
1723 QMouseEvent ev( QEvent::MouseMove, pos, event->globalPos(),
1724 event->button(), event->buttons(), event->modifiers() );
1725 plane->mouseMoveEvent( &ev );
1726 }
1727}
1728
1730{
1731 auto eventReceivers = QSet<AbstractCoordinatePlane*>(d->mouseClickedPlanes.cbegin(), d->mouseClickedPlanes.cend());
1732
1733 for ( AbstractCoordinatePlane* plane : std::as_const(d->coordinatePlanes) ) {
1734 if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
1735 eventReceivers.insert( plane );
1736 }
1737 }
1738
1739 const QPoint pos = mapFromGlobal( event->globalPos() );
1740
1741 for( AbstractCoordinatePlane* plane : std::as_const(eventReceivers) ) {
1742 QMouseEvent ev( QEvent::MouseButtonRelease, pos, event->globalPos(),
1743 event->button(), event->buttons(), event->modifiers() );
1744 plane->mouseReleaseEvent( &ev );
1745 }
1746
1747 d->mouseClickedPlanes.clear();
1748}
1749
1750bool Chart::event( QEvent* event )
1751{
1752 if ( event->type() == QEvent::ToolTip ) {
1753 const QHelpEvent* const helpEvent = static_cast< QHelpEvent* >( event );
1754 for( const AbstractCoordinatePlane* const plane : std::as_const(d->coordinatePlanes) ) {
1755 // iterate diagrams in reverse, so that the top-most painted diagram is
1756 // queried first for a tooltip before the diagrams behind it
1757 const ConstAbstractDiagramList& diagrams = plane->diagrams();
1758 for (int i = diagrams.size() - 1; i >= 0; --i) {
1759 const AbstractDiagram* diagram = diagrams[i];
1760 if (diagram->isHidden()) {
1761 continue;
1762 }
1763 const QModelIndex index = diagram->indexAt( helpEvent->pos() );
1764 const QVariant toolTip = index.data( Qt::ToolTipRole );
1765 if ( toolTip.isValid() ) {
1766 QPoint pos = mapFromGlobal( helpEvent->pos() );
1767 QRect rect( pos - QPoint( 1, 1 ), QSize( 3, 3 ) );
1768 QToolTip::showText( QCursor::pos(), toolTip.toString(), this, rect );
1769 return true;
1770 }
1771 }
1772 }
1773 }
1774 return QWidget::event( event );
1775}
1776
1777bool Chart::useNewLayoutSystem() const
1778{
1779 return d_func()->useNewLayoutSystem;
1780}
1781void Chart::setUseNewLayoutSystem( bool value )
1782{
1783 if ( d_func()->useNewLayoutSystem != value )
1784 d_func()->useNewLayoutSystem = value;
1785}
Definition of global enums.
PositionValue
Numerical values of the static KChart::Position instances, for using a Position::value() with a switc...
An area in the chart with a background, a frame, etc.
Base class for diagrams based on a cartesian coordianate system.
virtual KChart::CartesianAxisList axes() const
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane,...
void layoutPlanes()
Calling layoutPlanes() on the plane triggers the global KChart::Chart::slotLayoutPlanes()
void propertiesChanged()
Emitted upon change of a property of the Coordinate Plane or any of its components.
void setParent(Chart *parent)
Called internally by KChart::Chart.
void needRelayout()
Emitted when plane needs to trigger the Chart's layouting.
void destroyedCoordinatePlane(KChart::AbstractCoordinatePlane *)
Emitted when this coordinate plane is destroyed.
void needLayoutPlanes()
Emitted when plane needs to trigger the Chart's layouting of the coord.
void needUpdate()
Emitted when plane needs to update its drawings.
AbstractCoordinatePlane * referenceCoordinatePlane() const
There are two ways, in which planes can be caused to interact, in where they are put layouting wise: ...
AbstractDiagram defines the interface for diagram classes.
QModelIndex indexAt(const QPoint &point) const override
\reimpl
bool isHidden() const
Retrieve the hidden status specified globally.
Base class for all layout items of KChart.
Set of attributes usable for background pixmaps.
The class for cartesian axes.
A set of attributes for frames around items.
static QPaintDevice * paintDevice()
Return the paint device to use for calculating font metrics.
static void setPaintDevice(QPaintDevice *paintDevice)
Set the paint device to use for calculating font metrics.
A header or footer displaying text above or below charts.
Legend defines the interface for the legend drawing class.
void needSizeHint() override
Call this to trigger an conditional re-building of the widget's internals.
Qt::Alignment alignment() const
Returns the alignment of a non-floating legend.
void setReferenceArea(const QWidget *area)
Specifies the reference area for font size of title text, and for font size of the item texts,...
Position position() const
Returns the position of a non-floating legend.
Measure is used to specify relative and absolute sizes in KChart, e.g.
void setRelativeMode(const QObject *area, KChartEnums::MeasureOrientation orientation)
The reference area must either be derived from AbstractArea or from QWidget, so it can also be derive...
KChartEnums::PositionValue value() const
Returns an integer value corresponding to this Position.
Defines relative position information: reference area, position in this area (reference position),...
A text area in the chart with a background, a frame, etc.
A set of text attributes.
void setFontSize(const Measure &measure)
Set the size of the font used for rendering text.
void setTextAttributes(const TextAttributes &a)
Use this to specify the text attributes to be used for this item.
TextAttributes textAttributes() const
Returns the text attributes to be used for this item.
QAction * hint(const QObject *recvr, const char *slot, QObject *parent)
GeoCoordinates geo(const QVariant &location)
virtual void addItem(QLayoutItem *item) override
QPoint pos()
MouseButtonPress
virtual void addItem(QLayoutItem *item) override
void addLayout(QLayout *layout, int row, int column, Qt::Alignment alignment)
QLayoutItem * itemAtPosition(int row, int column) const const
void setColumnStretch(int column, int stretch)
void setRowStretch(int row, int stretch)
bool contains(const Key &key) const const
iterator insert(const Key &key, const T &value)
const QPoint & pos() const const
bool activate()
virtual int count() const const=0
virtual Qt::Orientations expandingDirections() const const override
virtual QRect geometry() const const override
virtual QLayoutItem * itemAt(int index) const const=0
virtual QSize maximumSize() const const override
virtual QSize minimumSize() const const override
bool setAlignment(QLayout *l, Qt::Alignment alignment)
void setContentsMargins(const QMargins &margins)
virtual void setGeometry(const QRect &r) override
Qt::Alignment alignment() const const
virtual Qt::Orientations expandingDirections() const const=0
virtual QRect geometry() const const=0
virtual bool hasHeightForWidth() const const
virtual void invalidate()
virtual QLayout * layout()
virtual QSize maximumSize() const const=0
virtual QSize minimumSize() const const=0
void setAlignment(Qt::Alignment alignment)
virtual QSize sizeHint() const const=0
virtual QSpacerItem * spacerItem()
void append(QList< T > &&value)
iterator begin()
void clear()
bool contains(const AT &value) const const
iterator end()
bool isEmpty() const const
qsizetype size() const const
const char * className() const const
QVariant data(int role) const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
virtual const QMetaObject * metaObject() const const
T qobject_cast(QObject *object)
void setObjectName(QAnyStringView name)
int logicalDpiX() const const
QPaintDevice * device() const const
void translate(const QPoint &offset)
virtual bool event(QEvent *ev) override
QPointF mapFromGlobal(const QPointF &point) const const
virtual void mouseDoubleClickEvent(QMouseEvent *event)
virtual void mouseMoveEvent(QMouseEvent *event)
virtual void mousePressEvent(QMouseEvent *event)
virtual void mouseReleaseEvent(QMouseEvent *event)
QSizeF size() const const
void update()
int height() const const
bool isEmpty() const const
int left() const const
int top() const const
int width() const const
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
int & rheight()
int & rwidth()
virtual QSpacerItem * spacerItem() override
void push(const T &t)
QString fromLatin1(QByteArrayView str)
QString number(double n, char format, int precision)
typedef Alignment
ToolTipRole
typedef Orientations
WA_WState_ExplicitShowHide
QTextStream & left(QTextStream &stream)
QTextStream & right(QTextStream &stream)
void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
bool isValid() const const
QString toString() const const
virtual bool event(QEvent *event) override
void setGeometry(const QRect &)
virtual int heightForWidth(int w) const const
bool isHidden() const const
virtual void resizeEvent(QResizeEvent *event)
void setParent(QWidget *parent)
void show()
bool testAttribute(Qt::WidgetAttribute attribute) const const
virtual QWidget * widget() const const override
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:02:46 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.