Qwt User's Guide 6.3.0
Loading...
Searching...
No Matches
qwt_plot_layout.cpp
1/******************************************************************************
2 * Qwt Widget Library
3 * Copyright (C) 1997 Josef Wilgen
4 * Copyright (C) 2002 Uwe Rathmann
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the Qwt License, Version 1.0
8 *****************************************************************************/
9
10#include "qwt_plot_layout.h"
11#include "qwt_text.h"
12#include "qwt_text_label.h"
13#include "qwt_scale_widget.h"
14#include "qwt_abstract_legend.h"
15#include "qwt_math.h"
16
17#include <qmargins.h>
18
19namespace
20{
21 class LayoutData
22 {
23 public:
24 struct LegendData
25 {
26 void init( const QwtAbstractLegend* legend )
27 {
28 if ( legend )
29 {
30 frameWidth = legend->frameWidth();
31 hScrollExtent = legend->scrollExtent( Qt::Horizontal );
32 vScrollExtent = legend->scrollExtent( Qt::Vertical );
33
34 hint = legend->sizeHint();
35 }
36 }
37
38 QSize legendHint( const QwtAbstractLegend* legend, const QRectF& rect ) const
39 {
40 const int w = qMin( hint.width(), qwtFloor( rect.width() ) );
41
42 int h = legend->heightForWidth( w );
43 if ( h <= 0 )
44 h = hint.height();
45
46 return QSize( w, h );
47 }
48
49 int frameWidth;
50 int hScrollExtent;
51 int vScrollExtent;
52 QSize hint;
53 };
54
55 struct LabelData
56 {
57 void init( const QwtTextLabel* label )
58 {
59 frameWidth = 0;
60 text = QwtText();
61
62 if ( label )
63 {
64 text = label->text();
65 if ( !( text.testPaintAttribute( QwtText::PaintUsingTextFont ) ) )
66 text.setFont( label->font() );
67
68 frameWidth = label->frameWidth();
69 }
70 }
71
72 QwtText text;
73 int frameWidth;
74 };
75
76 struct ScaleData
77 {
78 void init( const QwtScaleWidget* axisWidget )
79 {
80 isVisible = true;
81
82 scaleWidget = axisWidget;
83 scaleFont = axisWidget->font();
84
85 start = axisWidget->startBorderDist();
86 end = axisWidget->endBorderDist();
87
88 baseLineOffset = axisWidget->margin();
89
90 tickOffset = axisWidget->margin();
92 tickOffset += axisWidget->scaleDraw()->maxTickLength();
93
94 dimWithoutTitle = axisWidget->dimForLength( QWIDGETSIZE_MAX, scaleFont );
95 if ( !axisWidget->title().isEmpty() )
96 dimWithoutTitle -= axisWidget->titleHeightForWidth( QWIDGETSIZE_MAX );
97 }
98
99 void reset()
100 {
101 isVisible = false;
102 start = 0;
103 end = 0;
104 baseLineOffset = 0;
105 tickOffset = 0.0;
106 dimWithoutTitle = 0;
107 }
108
109 bool isVisible;
110 const QwtScaleWidget* scaleWidget;
111 QFont scaleFont;
112 int start;
113 int end;
114 int baseLineOffset;
115 double tickOffset;
116 int dimWithoutTitle;
117 };
118
119 struct CanvasData
120 {
121 void init( const QWidget* canvas )
122 {
123 const QMargins m = canvas->contentsMargins();
124
125 contentsMargins[ QwtAxis::YLeft ] = m.left();
126 contentsMargins[ QwtAxis::XTop ] = m.top();
127 contentsMargins[ QwtAxis::YRight ] = m.right();
128 contentsMargins[ QwtAxis::XBottom ] = m.bottom();
129 }
130
131 int contentsMargins[ QwtAxis::AxisPositions ];
132 };
133
134 public:
135 enum Label
136 {
137 Title,
138 Footer,
139
140 NumLabels
141 };
142
143 LayoutData( const QwtPlot* );
144 bool hasSymmetricYAxes() const;
145
146 inline ScaleData& axisData( QwtAxisId axisId )
147 {
148 return m_scaleData[ axisId ];
149 }
150
151 inline const ScaleData& axisData( QwtAxisId axisId ) const
152 {
153 return m_scaleData[ axisId ];
154 }
155
156 inline double tickOffset( int axisPos ) const
157 {
158 return axisData( axisPos ).tickOffset;
159 }
160
161 LegendData legendData;
162 LabelData labelData[ NumLabels ];
163 CanvasData canvasData;
164
165 private:
166 ScaleData m_scaleData[ QwtAxis::AxisPositions ];
167 };
168
169 /*
170 Extract all layout relevant data from the plot components
171 */
172 LayoutData::LayoutData( const QwtPlot* plot )
173 {
174 legendData.init( plot->legend() );
175 labelData[ Title ].init( plot->titleLabel() );
176 labelData[ Footer ].init( plot->footerLabel() );
177
178 for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ )
179 {
180 {
181 const QwtAxisId axisId( axisPos );
182
183 ScaleData& scaleData = axisData( axisId );
184
185 if ( plot->isAxisVisible( axisId ) )
186 {
187 const QwtScaleWidget* scaleWidget = plot->axisWidget( axisId );
188 scaleData.init( scaleWidget );
189 }
190 else
191 {
192 scaleData.reset();
193 }
194 }
195 }
196
197 canvasData.init( plot->canvas() );
198 }
199
200 bool LayoutData::hasSymmetricYAxes() const
201 {
202 using namespace QwtAxis;
203 return m_scaleData[ YLeft ].isVisible == m_scaleData[ YRight ].isVisible;
204 }
205}
206
207namespace
208{
209 class LayoutHintData
210 {
211 public:
212 LayoutHintData( const QwtPlot* plot );
213
214 int alignedSize( const QwtAxisId ) const;
215
216 inline int yAxesWidth() const
217 {
218 using namespace QwtAxis;
219 return axesWidth( YLeft ) + axesWidth( YRight );
220 }
221
222 inline int yAxesHeight() const
223 {
224 using namespace QwtAxis;
225 return qMax( axesHeight( YLeft ), axesHeight( YRight ) );
226 }
227
228 inline int xAxesHeight() const
229 {
230 using namespace QwtAxis;
231 return axesHeight( XTop ) + axesHeight( XBottom );
232 }
233
234 inline int xAxesWidth() const
235 {
236 using namespace QwtAxis;
237 return qMax( axesWidth( XTop ), axesWidth( XBottom ) );
238 }
239
240 private:
241
242 struct ScaleData
243 {
244 ScaleData()
245 {
246 w = h = minLeft = minRight = tickOffset = 0;
247 }
248
249 int w;
250 int h;
251 int minLeft;
252 int minRight;
253 int tickOffset;
254
255 };
256
257 const ScaleData& axisData( QwtAxisId axisId ) const
258 {
259 return m_scaleData[ axisId ];
260 }
261
262 ScaleData& axisData( QwtAxisId axisId )
263 {
264 return m_scaleData[ axisId ];
265 }
266
267 inline int axesWidth( int axisPos ) const
268 {
269 return m_scaleData[axisPos].w;
270 }
271
272 inline int axesHeight( int axisPos ) const
273 {
274 return m_scaleData[axisPos].h;
275 }
276
277 int m_canvasBorder[QwtAxis::AxisPositions];
278 ScaleData m_scaleData[QwtAxis::AxisPositions];
279 };
280
281 LayoutHintData::LayoutHintData( const QwtPlot* plot )
282 {
283 using namespace QwtAxis;
284
285 const QMargins m = plot->canvas()->contentsMargins();
286
287 int contentsMargins[ 4 ];
288 contentsMargins[ YLeft ] = m.left();
289 contentsMargins[ XTop ] = m.top();
290 contentsMargins[ YRight ] = m.right();
291 contentsMargins[ XBottom ] = m.bottom();
292
293 for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ )
294 {
295 m_canvasBorder[axisPos] = contentsMargins[axisPos] +
296 plot->plotLayout()->canvasMargin( axisPos ) + 1;
297 {
298 const QwtAxisId axisId( axisPos );
299
300 if ( plot->isAxisVisible( axisId ) )
301 {
302 const QwtScaleWidget* scl = plot->axisWidget( axisId );
303
304 const QSize hint = scl->minimumSizeHint();
305
306 ScaleData& sd = axisData( axisId );
307 sd.w = hint.width();
308 sd.h = hint.height();
309 scl->getBorderDistHint( sd.minLeft, sd.minRight );
310
311 {
312 sd.tickOffset = scl->margin();
314 sd.tickOffset += qwtCeil( scl->scaleDraw()->maxTickLength() );
315 }
316 }
317 }
318 }
319
320 for ( int axis = 0; axis < AxisPositions; axis++ )
321 {
322 const int sz = alignedSize( axis );
323
324 ScaleData& sd = axisData( axis );
325 if ( isXAxis( axis ) )
326 sd.w = sz;
327 else
328 sd.h = sz;
329 }
330 }
331
332 int LayoutHintData::alignedSize( const QwtAxisId axisId ) const
333 {
334 using namespace QwtAxis;
335
336 const ScaleData& sd = axisData( axisId );
337
338 if ( sd.w && isXAxis( axisId ) )
339 {
340 int w = sd.w;
341
342 if ( const int leftW = axesWidth( YLeft ) )
343 {
344 const int shiftLeft = sd.minLeft - m_canvasBorder[YLeft];
345 if ( shiftLeft > 0 )
346 w -= qMin( shiftLeft, leftW );
347 }
348
349 if ( const int rightW = axesWidth( YRight ) )
350 {
351 const int shiftRight = sd.minRight - m_canvasBorder[YRight];
352 if ( shiftRight > 0 )
353 w -= qMin( shiftRight, rightW );
354 }
355
356 return w;
357 }
358
359 if ( sd.h && isYAxis( axisId ) )
360 {
361 int h = sd.h;
362
363 if ( axesHeight( XBottom ) )
364 {
365 const int shiftBottom = sd.minLeft - m_canvasBorder[XBottom];
366 if ( shiftBottom > 0 )
367 h -= qMin( shiftBottom, axisData( XBottom ).tickOffset );
368 }
369
370 if ( axesHeight( XTop ) )
371 {
372 const int shiftTop = sd.minRight - m_canvasBorder[XTop];
373 if ( shiftTop > 0 )
374 h -= qMin( shiftTop, axisData( XTop ).tickOffset );
375 }
376
377 return h;
378 }
379
380 return 0;
381 }
382}
383
384namespace
385{
386 class LayoutEngine
387 {
388 public:
389 struct Dimensions
390 {
391 Dimensions()
392 {
393 dimTitle = dimFooter = 0;
394 for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ )
395 m_dimAxes[axisPos] = 0;
396 }
397
398 inline int dimAxis( QwtAxisId axisId ) const
399 {
400 return m_dimAxes[ axisId ];
401 }
402
403 void setDimAxis( QwtAxisId axisId, int dim )
404 {
405 m_dimAxes[ axisId ] = dim;
406 }
407
408 inline int dimAxes( int axisPos ) const
409 {
410 return m_dimAxes[ axisPos ];
411 }
412
413 inline int dimYAxes() const
414 {
415 return dimAxes( QwtAxis::YLeft ) + dimAxes( QwtAxis::YRight );
416 }
417
418 inline int dimXAxes() const
419 {
420 return dimAxes( QwtAxis::XTop ) + dimAxes( QwtAxis::XBottom );
421 }
422
423 inline QRectF centered( const QRectF& rect, const QRectF& labelRect ) const
424 {
425 QRectF r = labelRect;
426 r.setX( rect.left() + dimAxes( QwtAxis::YLeft ) );
427 r.setWidth( rect.width() - dimYAxes() );
428
429 return r;
430 }
431
432 inline QRectF innerRect( const QRectF& rect ) const
433 {
434 QRectF r(
435 rect.x() + dimAxes( QwtAxis::YLeft ),
436 rect.y() + dimAxes( QwtAxis::XTop ),
437 rect.width() - dimYAxes(),
438 rect.height() - dimXAxes() );
439
440 if ( r.width() < 0 )
441 {
442 r.setX( rect.center().x() );
443 r.setWidth( 0 );
444 }
445 if ( r.height() < 0 )
446 {
447 r.setY( rect.center().y() );
448 r.setHeight( 0 );
449 }
450
451 return r;
452 }
453
454 int dimTitle;
455 int dimFooter;
456
457 private:
458 int m_dimAxes[QwtAxis::AxisPositions];
459 };
460
461 LayoutEngine()
462 : m_legendPos( QwtPlot::BottomLegend )
463 , m_legendRatio( 1.0 )
464 , m_spacing( 5 )
465 {
466 }
467
468 QRectF layoutLegend( QwtPlotLayout::Options,
469 const LayoutData::LegendData&, const QRectF&, const QSize& ) const;
470
471 QRectF alignLegend( const QSize& legendHint,
472 const QRectF& canvasRect, const QRectF& legendRect ) const;
473
474 void alignScales( QwtPlotLayout::Options,
475 const LayoutData&, QRectF& canvasRect,
476 QRectF scaleRect[QwtAxis::AxisPositions] ) const;
477
478 Dimensions layoutDimensions( QwtPlotLayout::Options,
479 const LayoutData&, const QRectF& ) const;
480
481 inline void setSpacing( unsigned int spacing ) { m_spacing = spacing; }
482 inline unsigned int spacing() const { return m_spacing; }
483
484 inline void setAlignCanvas( int axisPos, bool on ) { m_alignCanvas[ axisPos ] = on; }
485 inline bool alignCanvas( int axisPos ) const { return m_alignCanvas[ axisPos ]; }
486
487 inline void setCanvasMargin( int axisPos, int margin ) { m_canvasMargin[ axisPos ] = margin; }
488 inline int canvasMargin( int axisPos ) const { return m_canvasMargin[ axisPos ]; }
489
490 inline void setLegendPos( QwtPlot::LegendPosition pos ) { m_legendPos = pos; }
491 inline QwtPlot::LegendPosition legendPos() const { return m_legendPos; }
492
493 inline void setLegendRatio( double ratio ) { m_legendRatio = ratio; }
494 inline double legendRatio() const { return m_legendRatio; }
495
496 private:
497 int heightForWidth( LayoutData::Label, const LayoutData&,
498 QwtPlotLayout::Options, double width, int axesWidth ) const;
499
500 QwtPlot::LegendPosition m_legendPos;
501 double m_legendRatio;
502
503 unsigned int m_canvasMargin[QwtAxis::AxisPositions];
504 bool m_alignCanvas[QwtAxis::AxisPositions];
505
506 unsigned int m_spacing;
507 };
508}
509
510QRectF LayoutEngine::layoutLegend( QwtPlotLayout::Options options,
511 const LayoutData::LegendData& legendData,
512 const QRectF& rect, const QSize& legendHint ) const
513{
514 int dim;
515 if ( m_legendPos == QwtPlot::LeftLegend
516 || m_legendPos == QwtPlot::RightLegend )
517 {
518 // We don't allow vertical legends to take more than
519 // half of the available space.
520
521 dim = qMin( legendHint.width(), int( rect.width() * m_legendRatio ) );
522
523 if ( !( options & QwtPlotLayout::IgnoreScrollbars ) )
524 {
525 if ( legendHint.height() > rect.height() )
526 {
527 // The legend will need additional
528 // space for the vertical scrollbar.
529
530 dim += legendData.hScrollExtent;
531 }
532 }
533 }
534 else
535 {
536 dim = qMin( legendHint.height(), int( rect.height() * m_legendRatio ) );
537 dim = qMax( dim, legendData.vScrollExtent );
538 }
539
540 QRectF legendRect = rect;
541 switch ( m_legendPos )
542 {
544 {
545 legendRect.setWidth( dim );
546 break;
547 }
549 {
550 legendRect.setX( rect.right() - dim );
551 legendRect.setWidth( dim );
552 break;
553 }
555 {
556 legendRect.setHeight( dim );
557 break;
558 }
560 {
561 legendRect.setY( rect.bottom() - dim );
562 legendRect.setHeight( dim );
563 break;
564 }
565 }
566
567 return legendRect;
568}
569
570QRectF LayoutEngine::alignLegend( const QSize& legendHint,
571 const QRectF& canvasRect, const QRectF& legendRect ) const
572{
573 QRectF alignedRect = legendRect;
574
575 if ( m_legendPos == QwtPlot::BottomLegend
576 || m_legendPos == QwtPlot::TopLegend )
577 {
578 if ( legendHint.width() < canvasRect.width() )
579 {
580 alignedRect.setX( canvasRect.x() );
581 alignedRect.setWidth( canvasRect.width() );
582 }
583 }
584 else
585 {
586 if ( legendHint.height() < canvasRect.height() )
587 {
588 alignedRect.setY( canvasRect.y() );
589 alignedRect.setHeight( canvasRect.height() );
590 }
591 }
592
593 return alignedRect;
594}
595
596int LayoutEngine::heightForWidth(
597 LayoutData::Label labelType, const LayoutData& layoutData,
599 double width, int axesWidth ) const
600{
601 const LayoutData::LabelData& labelData = layoutData.labelData[ labelType ];
602
603 if ( labelData.text.isEmpty() )
604 return 0;
605
606 double w = width;
607
608 if ( !layoutData.hasSymmetricYAxes() )
609 {
610 // center to the canvas
611 w -= axesWidth;
612 }
613
614 int d = qwtCeil( labelData.text.heightForWidth( w ) );
615 if ( !( options & QwtPlotLayout::IgnoreFrames ) )
616 d += 2 * labelData.frameWidth;
617
618 return d;
619}
620
621LayoutEngine::Dimensions LayoutEngine::layoutDimensions( QwtPlotLayout::Options options,
622 const LayoutData& layoutData, const QRectF& rect ) const
623{
624 using namespace QwtAxis;
625
626 Dimensions dimensions;
627
628 int backboneOffset[AxisPositions];
629 for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ )
630 {
631 backboneOffset[axisPos] = 0;
632 if ( !( options & QwtPlotLayout::IgnoreFrames ) )
633 backboneOffset[axisPos] += layoutData.canvasData.contentsMargins[axisPos];
634
635 if ( !m_alignCanvas[axisPos] )
636 backboneOffset[axisPos] += m_canvasMargin[axisPos];
637 }
638
639 bool done = false;
640 while ( !done )
641 {
642 done = true;
643
644 // the size for the 4 axis depend on each other. Expanding
645 // the height of a horizontal axis will shrink the height
646 // for the vertical axis, shrinking the height of a vertical
647 // axis will result in a line break what will expand the
648 // width and results in shrinking the width of a horizontal
649 // axis what might result in a line break of a horizontal
650 // axis ... . So we loop as long until no size changes.
651
652 if ( !( options & QwtPlotLayout::IgnoreTitle ) )
653 {
654 const int d = heightForWidth(
655 LayoutData::Title, layoutData, options,
656 rect.width(), dimensions.dimYAxes() );
657
658 if ( d > dimensions.dimTitle )
659 {
660 dimensions.dimTitle = d;
661 done = false;
662 }
663 }
664
665 if ( !( options & QwtPlotLayout::IgnoreFooter ) )
666 {
667 const int d = heightForWidth(
668 LayoutData::Footer, layoutData, options,
669 rect.width(), dimensions.dimYAxes() );
670
671 if ( d > dimensions.dimFooter )
672 {
673 dimensions.dimFooter = d;
674 done = false;
675 }
676 }
677
678 for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ )
679 {
680 {
681 const QwtAxisId axisId( axisPos );
682
683 const LayoutData::ScaleData& scaleData = layoutData.axisData( axisId );
684
685 if ( scaleData.isVisible )
686 {
687 double length;
688 if ( isXAxis( axisPos ) )
689 {
690 length = rect.width() - dimensions.dimYAxes();
691 length -= scaleData.start + scaleData.end;
692
693 if ( dimensions.dimAxes( YRight ) > 0 )
694 length -= 1;
695
696 length += qMin( dimensions.dimAxes( YLeft ),
697 scaleData.start - backboneOffset[YLeft] );
698
699 length += qMin( dimensions.dimAxes( YRight ),
700 scaleData.end - backboneOffset[YRight] );
701 }
702 else // y axis
703 {
704 length = rect.height() - dimensions.dimXAxes();
705 length -= scaleData.start + scaleData.end;
706 length -= 1;
707
708 if ( dimensions.dimAxes( XBottom ) <= 0 )
709 length -= 1;
710
711 if ( dimensions.dimAxes( XTop ) <= 0 )
712 length -= 1;
713
714 /*
715 The tick labels of the y axes are always left/right from the
716 backbone/ticks of the x axes - but we have to take care,
717 that the labels don't overlap.
718 */
719 if ( dimensions.dimAxes( XBottom ) > 0 )
720 {
721 length += qMin(
722 layoutData.tickOffset( XBottom ),
723 double( scaleData.start - backboneOffset[XBottom] ) );
724 }
725
726 if ( dimensions.dimAxes( XTop ) > 0 )
727 {
728 length += qMin(
729 layoutData.tickOffset( XTop ),
730 double( scaleData.end - backboneOffset[XTop] ) );
731 }
732
733 if ( dimensions.dimTitle > 0 )
734 length -= dimensions.dimTitle + m_spacing;
735 }
736
737 int d = scaleData.dimWithoutTitle;
738 if ( !scaleData.scaleWidget->title().isEmpty() )
739 {
740 d += scaleData.scaleWidget->titleHeightForWidth( qwtFloor( length ) );
741 }
742
743
744 if ( d > dimensions.dimAxis( axisId ) )
745 {
746 dimensions.setDimAxis( axisId, d );
747 done = false;
748 }
749 }
750 }
751 }
752 }
753
754 return dimensions;
755}
756
757void LayoutEngine::alignScales( QwtPlotLayout::Options options,
758 const LayoutData& layoutData, QRectF& canvasRect,
759 QRectF scaleRect[QwtAxis::AxisPositions] ) const
760{
761 using namespace QwtAxis;
762
763 int backboneOffset[AxisPositions];
764 for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ )
765 {
766 backboneOffset[axisPos] = 0;
767
768 if ( !m_alignCanvas[axisPos] )
769 {
770 backboneOffset[axisPos] += m_canvasMargin[axisPos];
771 }
772
773 if ( !( options & QwtPlotLayout::IgnoreFrames ) )
774 {
775 backboneOffset[axisPos] +=
776 layoutData.canvasData.contentsMargins[axisPos];
777 }
778 }
779
780 for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ )
781 {
782 {
783 QRectF& axisRect = scaleRect[axisPos];
784 if ( !axisRect.isValid() )
785 continue;
786
787 const QwtAxisId axisId( axisPos );
788
789 const int startDist = layoutData.axisData( axisId ).start;
790 const int endDist = layoutData.axisData( axisId ).end;
791
792 if ( isXAxis( axisPos ) )
793 {
794 const QRectF& leftScaleRect = scaleRect[YLeft];
795 const int leftOffset = backboneOffset[YLeft] - startDist;
796
797 if ( leftScaleRect.isValid() )
798 {
799 const double dx = leftOffset + leftScaleRect.width();
800 if ( m_alignCanvas[YLeft] && dx < 0.0 )
801 {
802 /*
803 The axis needs more space than the width
804 of the left scale.
805 */
806 const double cLeft = canvasRect.left(); // qreal -> double
807 canvasRect.setLeft( qwtMaxF( cLeft, axisRect.left() - dx ) );
808 }
809 else
810 {
811 const double minLeft = leftScaleRect.left();
812 const double left = axisRect.left() + leftOffset;
813 axisRect.setLeft( qwtMaxF( left, minLeft ) );
814 }
815 }
816 else
817 {
818 if ( m_alignCanvas[YLeft] && leftOffset < 0 )
819 {
820 canvasRect.setLeft( qwtMaxF( canvasRect.left(),
821 axisRect.left() - leftOffset ) );
822 }
823 else
824 {
825 if ( leftOffset > 0 )
826 axisRect.setLeft( axisRect.left() + leftOffset );
827 }
828 }
829
830 const QRectF& rightScaleRect = scaleRect[YRight];
831 const int rightOffset = backboneOffset[YRight] - endDist + 1;
832
833 if ( rightScaleRect.isValid() )
834 {
835 const double dx = rightOffset + rightScaleRect.width();
836 if ( m_alignCanvas[YRight] && dx < 0 )
837 {
838 /*
839 The axis needs more space than the width
840 of the right scale.
841 */
842 const double cRight = canvasRect.right(); // qreal -> double
843 canvasRect.setRight( qwtMinF( cRight, axisRect.right() + dx ) );
844 }
845
846 const double maxRight = rightScaleRect.right();
847 const double right = axisRect.right() - rightOffset;
848 axisRect.setRight( qwtMinF( right, maxRight ) );
849 }
850 else
851 {
852 if ( m_alignCanvas[YRight] && rightOffset < 0 )
853 {
854 canvasRect.setRight( qwtMinF( canvasRect.right(),
855 axisRect.right() + rightOffset ) );
856 }
857 else
858 {
859 if ( rightOffset > 0 )
860 axisRect.setRight( axisRect.right() - rightOffset );
861 }
862 }
863 }
864 else // y axes
865 {
866 const QRectF& bottomScaleRect = scaleRect[XBottom];
867 const int bottomOffset = backboneOffset[XBottom] - endDist + 1;
868
869 if ( bottomScaleRect.isValid() )
870 {
871 const double dy = bottomOffset + bottomScaleRect.height();
872 if ( m_alignCanvas[XBottom] && dy < 0 )
873 {
874 /*
875 The axis needs more space than the height
876 of the bottom scale.
877 */
878 const double cBottom = canvasRect.bottom(); // qreal -> double
879 canvasRect.setBottom( qwtMinF( cBottom, axisRect.bottom() + dy ) );
880 }
881 else
882 {
883 const double maxBottom = bottomScaleRect.top() +
884 layoutData.tickOffset( XBottom );
885 const double bottom = axisRect.bottom() - bottomOffset;
886 axisRect.setBottom( qwtMinF( bottom, maxBottom ) );
887 }
888 }
889 else
890 {
891 if ( m_alignCanvas[XBottom] && bottomOffset < 0 )
892 {
893 canvasRect.setBottom( qwtMinF( canvasRect.bottom(),
894 axisRect.bottom() + bottomOffset ) );
895 }
896 else
897 {
898 if ( bottomOffset > 0 )
899 axisRect.setBottom( axisRect.bottom() - bottomOffset );
900 }
901 }
902
903 const QRectF& topScaleRect = scaleRect[XTop];
904 const int topOffset = backboneOffset[XTop] - startDist;
905
906 if ( topScaleRect.isValid() )
907 {
908 const double dy = topOffset + topScaleRect.height();
909 if ( m_alignCanvas[XTop] && dy < 0 )
910 {
911 /*
912 The axis needs more space than the height
913 of the top scale.
914 */
915 const double cTop = canvasRect.top(); // qreal -> double
916 canvasRect.setTop( qwtMaxF( cTop, axisRect.top() - dy ) );
917 }
918 else
919 {
920 const double minTop = topScaleRect.bottom() -
921 layoutData.tickOffset( XTop );
922
923 const double top = axisRect.top() + topOffset;
924 axisRect.setTop( qwtMaxF( top, minTop ) );
925 }
926 }
927 else
928 {
929 if ( m_alignCanvas[XTop] && topOffset < 0 )
930 {
931 canvasRect.setTop( qwtMaxF( canvasRect.top(),
932 axisRect.top() - topOffset ) );
933 }
934 else
935 {
936 if ( topOffset > 0 )
937 axisRect.setTop( axisRect.top() + topOffset );
938 }
939 }
940 }
941 }
942 }
943
944 /*
945 The canvas has been aligned to the scale with largest
946 border distances. Now we have to realign the other scale.
947 */
948
949 for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ )
950 {
951 {
952 const QwtAxisId axisId( axisPos );
953
954 QRectF& sRect = scaleRect[axisPos];
955 const LayoutData::ScaleData& axisData = layoutData.axisData( axisId );
956
957 if ( !sRect.isValid() )
958 continue;
959
960 if ( isXAxis( axisPos ) )
961 {
962 if ( m_alignCanvas[YLeft] )
963 {
964 double y = canvasRect.left() - axisData.start;
965 if ( !( options & QwtPlotLayout::IgnoreFrames ) )
966 y += layoutData.canvasData.contentsMargins[YLeft];
967
968 sRect.setLeft( y );
969 }
970
971 if ( m_alignCanvas[YRight] )
972 {
973 double y = canvasRect.right() - 1 + axisData.end;
974 if ( !( options & QwtPlotLayout::IgnoreFrames ) )
975 y -= layoutData.canvasData.contentsMargins[YRight];
976
977 sRect.setRight( y );
978 }
979
980 if ( m_alignCanvas[axisPos] )
981 {
982 if ( axisPos == XTop )
983 sRect.setBottom( canvasRect.top() );
984 else
985 sRect.setTop( canvasRect.bottom() );
986 }
987 }
988 else
989 {
990 if ( m_alignCanvas[XTop] )
991 {
992 double x = canvasRect.top() - axisData.start;
993 if ( !( options & QwtPlotLayout::IgnoreFrames ) )
994 x += layoutData.canvasData.contentsMargins[XTop];
995
996 sRect.setTop( x );
997 }
998
999 if ( m_alignCanvas[XBottom] )
1000 {
1001 double x = canvasRect.bottom() - 1 + axisData.end;
1002 if ( !( options & QwtPlotLayout::IgnoreFrames ) )
1003 x -= layoutData.canvasData.contentsMargins[XBottom];
1004
1005 sRect.setBottom( x );
1006 }
1007
1008 if ( m_alignCanvas[axisPos] )
1009 {
1010 if ( axisPos == YLeft )
1011 sRect.setRight( canvasRect.left() );
1012 else
1013 sRect.setLeft( canvasRect.right() );
1014 }
1015 }
1016 }
1017 }
1018}
1019
1020class QwtPlotLayout::PrivateData
1021{
1022 public:
1023 QRectF titleRect;
1024 QRectF footerRect;
1025 QRectF legendRect;
1026 QRectF scaleRects[QwtAxis::AxisPositions];
1027 QRectF canvasRect;
1028
1029 LayoutEngine engine;
1030};
1031
1037{
1038 m_data = new PrivateData;
1039
1040 setLegendPosition( QwtPlot::BottomLegend );
1041 setCanvasMargin( 4 );
1042 setAlignCanvasToScales( false );
1043
1044 invalidate();
1045}
1046
1049{
1050 delete m_data;
1051}
1052
1066void QwtPlotLayout::setCanvasMargin( int margin, int axisPos )
1067{
1068 if ( margin < -1 )
1069 margin = -1;
1070
1071 LayoutEngine& engine = m_data->engine;
1072
1073 if ( axisPos == -1 )
1074 {
1075 for ( axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ )
1076 engine.setCanvasMargin( axisPos, margin );
1077 }
1078 else if ( QwtAxis::isValid( axisPos ) )
1079 {
1080 engine.setCanvasMargin( axisPos, margin );
1081 }
1082}
1083
1089int QwtPlotLayout::canvasMargin( int axisPos ) const
1090{
1091 if ( !QwtAxis::isValid( axisPos ) )
1092 return 0;
1093
1094 return m_data->engine.canvasMargin( axisPos );
1095}
1096
1104{
1105 for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ )
1106 m_data->engine.setAlignCanvas( axisPos, on );
1107}
1108
1126void QwtPlotLayout::setAlignCanvasToScale( int axisPos, bool on )
1127{
1128 if ( QwtAxis::isValid( axisPos ) )
1129 m_data->engine.setAlignCanvas( axisPos, on );
1130}
1131
1141bool QwtPlotLayout::alignCanvasToScale( int axisPos ) const
1142{
1143 if ( !QwtAxis::isValid( axisPos ) )
1144 return false;
1145
1146 return m_data->engine.alignCanvas( axisPos );
1147}
1148
1156void QwtPlotLayout::setSpacing( int spacing )
1157{
1158 m_data->engine.setSpacing( qMax( 0, spacing ) );
1159}
1160
1166{
1167 return m_data->engine.spacing();
1168}
1169
1184{
1185 if ( ratio > 1.0 )
1186 ratio = 1.0;
1187
1188 LayoutEngine& engine = m_data->engine;
1189
1190 switch ( pos )
1191 {
1192 case QwtPlot::TopLegend:
1194 {
1195 if ( ratio <= 0.0 )
1196 ratio = 0.33;
1197
1198 engine.setLegendRatio( ratio );
1199 engine.setLegendPos( pos );
1200
1201 break;
1202 }
1205 {
1206 if ( ratio <= 0.0 )
1207 ratio = 0.5;
1208
1209 engine.setLegendRatio( ratio );
1210 engine.setLegendPos( pos );
1211
1212 break;
1213 }
1214 default:
1215 break;
1216 }
1217}
1218
1228{
1229 setLegendPosition( pos, 0.0 );
1230}
1231
1238{
1239 return m_data->engine.legendPos();
1240}
1241
1252{
1253 setLegendPosition( legendPosition(), ratio );
1254}
1255
1261{
1262 return m_data->engine.legendRatio();
1263}
1264
1273void QwtPlotLayout::setTitleRect( const QRectF& rect )
1274{
1275 m_data->titleRect = rect;
1276}
1277
1283{
1284 return m_data->titleRect;
1285}
1286
1295void QwtPlotLayout::setFooterRect( const QRectF& rect )
1296{
1297 m_data->footerRect = rect;
1298}
1299
1305{
1306 return m_data->footerRect;
1307}
1308
1319void QwtPlotLayout::setLegendRect( const QRectF& rect )
1320{
1321 m_data->legendRect = rect;
1322}
1323
1329{
1330 return m_data->legendRect;
1331}
1332
1344void QwtPlotLayout::setScaleRect( QwtAxisId axisId, const QRectF& rect )
1345{
1346 if ( QwtAxis::isValid( axisId ) )
1347 m_data->scaleRects[axisId] = rect;
1348}
1349
1355QRectF QwtPlotLayout::scaleRect( QwtAxisId axisId ) const
1356{
1357 if ( QwtAxis::isValid( axisId ) )
1358 return m_data->scaleRects[axisId];
1359
1360 return QRectF();
1361}
1362
1371void QwtPlotLayout::setCanvasRect( const QRectF& rect )
1372{
1373 m_data->canvasRect = rect;
1374}
1375
1381{
1382 return m_data->canvasRect;
1383}
1384
1390{
1391 m_data->titleRect = m_data->footerRect =
1392 m_data->legendRect = m_data->canvasRect = QRectF();
1393
1394 for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ )
1395 m_data->scaleRects[axisPos] = QRect();
1396}
1397
1404QSize QwtPlotLayout::minimumSizeHint( const QwtPlot* plot ) const
1405{
1406 LayoutHintData hintData( plot );
1407
1408 const int xAxesWidth = hintData.xAxesWidth();
1409 const int yAxesHeight = hintData.yAxesHeight();
1410
1411 const QWidget* canvas = plot->canvas();
1412
1413 const QMargins m = canvas->contentsMargins();
1414 const QSize minCanvasSize = canvas->minimumSize();
1415
1416 int w = hintData.yAxesWidth();
1417 int cw = xAxesWidth + m.left() + 1 + m.right() + 1;
1418 w += qMax( cw, minCanvasSize.width() );
1419
1420 int h = hintData.xAxesHeight();
1421 int ch = yAxesHeight + m.top() + 1 + m.bottom() + 1;
1422 h += qMax( ch, minCanvasSize.height() );
1423
1424 const QwtTextLabel* labels[2];
1425 labels[0] = plot->titleLabel();
1426 labels[1] = plot->footerLabel();
1427
1428 for ( int i = 0; i < 2; i++ )
1429 {
1430 const QwtTextLabel* label = labels[i];
1431 if ( label && !label->text().isEmpty() )
1432 {
1433 // we center on the plot canvas.
1434 const bool centerOnCanvas = !( plot->isAxisVisible( QwtAxis::YLeft )
1435 && plot->isAxisVisible( QwtAxis::YRight ) );
1436
1437 int labelW = w;
1438 if ( centerOnCanvas )
1439 {
1440 labelW -= hintData.yAxesWidth();
1441 }
1442
1443 int labelH = label->heightForWidth( labelW );
1444 if ( labelH > labelW ) // Compensate for a long title
1445 {
1446 w = labelW = labelH;
1447 if ( centerOnCanvas )
1448 w += hintData.yAxesWidth();
1449
1450 labelH = label->heightForWidth( labelW );
1451 }
1452 h += labelH + spacing();
1453 }
1454 }
1455
1456 // Compute the legend contribution
1457
1458 const QwtAbstractLegend* legend = plot->legend();
1459 if ( legend && !legend->isEmpty() )
1460 {
1461 const LayoutEngine& engine = m_data->engine;
1462
1463 if ( engine.legendPos() == QwtPlot::LeftLegend
1464 || engine.legendPos() == QwtPlot::RightLegend )
1465 {
1466 int legendW = legend->sizeHint().width();
1467 int legendH = legend->heightForWidth( legendW );
1468
1469 if ( legend->frameWidth() > 0 )
1470 w += spacing();
1471
1472 if ( legendH > h )
1473 legendW += legend->scrollExtent( Qt::Horizontal );
1474
1475 if ( engine.legendRatio() < 1.0 )
1476 legendW = qMin( legendW, int( w / ( 1.0 - engine.legendRatio() ) ) );
1477
1478 w += legendW + spacing();
1479 }
1480 else
1481 {
1482 int legendW = qMin( legend->sizeHint().width(), w );
1483 int legendH = legend->heightForWidth( legendW );
1484
1485 if ( legend->frameWidth() > 0 )
1486 h += spacing();
1487
1488 if ( engine.legendRatio() < 1.0 )
1489 legendH = qMin( legendH, int( h / ( 1.0 - engine.legendRatio() ) ) );
1490
1491 h += legendH + spacing();
1492 }
1493 }
1494
1495 return QSize( w, h );
1496}
1497
1509 const QRectF& plotRect, Options options )
1510{
1511 invalidate();
1512
1513 QRectF rect( plotRect ); // undistributed rest of the plot rect
1514
1515 // We extract all layout relevant parameters from the widgets,
1516 // and save them to m_data->layoutData.
1517
1518 LayoutData layoutData( plot );
1519
1520 QSize legendHint;
1521
1522 if ( !( options & IgnoreLegend )
1523 && plot->legend() && !plot->legend()->isEmpty() )
1524 {
1525 legendHint = layoutData.legendData.legendHint( plot->legend(), rect );
1526
1527 m_data->legendRect = m_data->engine.layoutLegend(
1528 options, layoutData.legendData, rect, legendHint );
1529
1530 // subtract m_data->legendRect from rect
1531
1532 const QRegion region( rect.toRect() );
1533 rect = region.subtracted( m_data->legendRect.toRect() ).boundingRect();
1534
1535 switch ( m_data->engine.legendPos() )
1536 {
1538 {
1539 rect.setLeft( rect.left() + spacing() );
1540 break;
1541 }
1543 {
1544 rect.setRight( rect.right() - spacing() );
1545 break;
1546 }
1547 case QwtPlot::TopLegend:
1548 {
1549 rect.setTop( rect.top() + spacing() );
1550 break;
1551 }
1553 {
1554 rect.setBottom( rect.bottom() - spacing() );
1555 break;
1556 }
1557 }
1558 }
1559
1560 /*
1561 +---+-----------+---+
1562 | Title |
1563 +---+-----------+---+
1564 | | Axis | |
1565 +---+-----------+---+
1566 | A | | A |
1567 | x | Canvas | x |
1568 | i | | i |
1569 | s | | s |
1570 +---+-----------+---+
1571 | | Axis | |
1572 +---+-----------+---+
1573 | Footer |
1574 +---+-----------+---+
1575 */
1576
1577 // title, footer and axes include text labels. The height of each
1578 // label depends on its line breaks, that depend on the width
1579 // for the label. A line break in a horizontal text will reduce
1580 // the available width for vertical texts and vice versa.
1581 // layoutDimensions finds the height/width for title, footer and axes
1582 // including all line breaks.
1583
1584 using namespace QwtAxis;
1585
1586 const LayoutEngine::Dimensions dimensions =
1587 m_data->engine.layoutDimensions( options, layoutData, rect );
1588
1589 if ( dimensions.dimTitle > 0 )
1590 {
1591 QRectF& labelRect = m_data->titleRect;
1592
1593 labelRect.setRect( rect.left(), rect.top(), rect.width(), dimensions.dimTitle );
1594
1595 rect.setTop( labelRect.bottom() + spacing() );
1596
1597 if ( !layoutData.hasSymmetricYAxes() )
1598 {
1599 // if only one of the y axes is missing we align
1600 // the title centered to the canvas
1601
1602 labelRect = dimensions.centered( rect, labelRect );
1603 }
1604 }
1605
1606 if ( dimensions.dimFooter > 0 )
1607 {
1608 QRectF& labelRect = m_data->footerRect;
1609
1610 labelRect.setRect( rect.left(), rect.bottom() - dimensions.dimFooter,
1611 rect.width(), dimensions.dimFooter );
1612
1613 rect.setBottom( labelRect.top() - spacing() );
1614
1615 if ( !layoutData.hasSymmetricYAxes() )
1616 {
1617 // if only one of the y axes is missing we align
1618 // the footer centered to the canvas
1619
1620 labelRect = dimensions.centered( rect, labelRect );
1621 }
1622 }
1623
1624 m_data->canvasRect = dimensions.innerRect( rect );
1625
1626 for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ )
1627 {
1628 // set the rects for the axes
1629
1630 const int pos = 0;
1631 {
1632 const QwtAxisId axisId( axisPos );
1633
1634 if ( dimensions.dimAxis( axisId ) )
1635 {
1636 const int dim = dimensions.dimAxis( axisId );
1637
1638 const QRectF& canvasRect = m_data->canvasRect;
1639
1640 QRectF& scaleRect = m_data->scaleRects[axisId];
1641 scaleRect = canvasRect;
1642
1643 switch ( axisPos )
1644 {
1645 case YLeft:
1646 {
1647 scaleRect.setX( canvasRect.left() - pos - dim );
1648 scaleRect.setWidth( dim );
1649 break;
1650 }
1651 case YRight:
1652 {
1653 scaleRect.setX( canvasRect.right() + pos );
1654 scaleRect.setWidth( dim );
1655 break;
1656 }
1657 case XBottom:
1658 {
1659 scaleRect.setY( canvasRect.bottom() + pos );
1660 scaleRect.setHeight( dim );
1661 break;
1662 }
1663 case XTop:
1664 {
1665 scaleRect.setY( canvasRect.top() - pos - dim );
1666 scaleRect.setHeight( dim );
1667 break;
1668 }
1669 }
1670 scaleRect = scaleRect.normalized();
1671 }
1672 }
1673 }
1674
1675 // +---+-----------+---+
1676 // | <- Axis -> |
1677 // +-^-+-----------+-^-+
1678 // | | | | | |
1679 // | | | |
1680 // | A | | A |
1681 // | x | Canvas | x |
1682 // | i | | i |
1683 // | s | | s |
1684 // | | | |
1685 // | | | | | |
1686 // +-V-+-----------+-V-+
1687 // | <- Axis -> |
1688 // +---+-----------+---+
1689
1690 // The ticks of the axes - not the labels above - should
1691 // be aligned to the canvas. So we try to use the empty
1692 // corners to extend the axes, so that the label texts
1693 // left/right of the min/max ticks are moved into them.
1694
1695 m_data->engine.alignScales( options, layoutData,
1696 m_data->canvasRect, m_data->scaleRects );
1697
1698 if ( !m_data->legendRect.isEmpty() )
1699 {
1700 // We prefer to align the legend to the canvas - not to
1701 // the complete plot - if possible.
1702
1703 m_data->legendRect = m_data->engine.alignLegend(
1704 legendHint, m_data->canvasRect, m_data->legendRect );
1705 }
1706}
Abstract base class for legend widgets.
virtual int scrollExtent(Qt::Orientation) const
virtual bool isEmpty() const =0
bool hasComponent(ScaleComponent) const
A 2-D plotting widget.
Definition qwt_plot.h:79
LegendPosition
Definition qwt_plot.h:94
@ TopLegend
The legend will be above the title.
Definition qwt_plot.h:105
@ LeftLegend
The legend will be left from the QwtAxis::YLeft axis.
Definition qwt_plot.h:96
@ RightLegend
The legend will be right from the QwtAxis::YRight axis.
Definition qwt_plot.h:99
@ BottomLegend
The legend will be below the footer.
Definition qwt_plot.h:102
QWidget * canvas()
Definition qwt_plot.cpp:463
QwtTextLabel * footerLabel()
Definition qwt_plot.cpp:401
bool isAxisVisible(QwtAxisId) const
QwtPlotLayout * plotLayout()
Definition qwt_plot.cpp:430
QwtAbstractLegend * legend()
Definition qwt_plot.cpp:445
const QwtScaleWidget * axisWidget(QwtAxisId) const
QwtTextLabel * titleLabel()
Definition qwt_plot.cpp:357
QRectF titleRect() const
void setCanvasMargin(int margin, int axis=-1)
void setLegendPosition(QwtPlot::LegendPosition pos, double ratio)
Specify the position of the legend.
void setAlignCanvasToScales(bool)
Set the align-canvas-to-axis-scales flag for all axes.
QRectF footerRect() const
int canvasMargin(int axisId) const
QRectF legendRect() const
virtual ~QwtPlotLayout()
Destructor.
virtual void invalidate()
QwtPlot::LegendPosition legendPosition() const
bool alignCanvasToScale(int axisId) const
virtual QSize minimumSizeHint(const QwtPlot *) const
void setLegendRect(const QRectF &)
Set the geometry for the legend.
QRectF scaleRect(QwtAxisId) const
void setFooterRect(const QRectF &)
Set the geometry for the footer.
void setTitleRect(const QRectF &)
Set the geometry for the title.
double legendRatio() const
QFlags< Option > Options
void setLegendRatio(double ratio)
QRectF canvasRect() const
void setCanvasRect(const QRectF &)
Set the geometry for the canvas.
void setScaleRect(QwtAxisId, const QRectF &)
Set the geometry for an axis.
QwtPlotLayout()
Constructor.
@ IgnoreTitle
Ignore the title.
@ IgnoreFooter
Ignore the footer.
@ IgnoreFrames
Ignore all frames.
virtual void activate(const QwtPlot *, const QRectF &plotRect, Options options=Options())
Recalculate the geometry of all components.
void setAlignCanvasToScale(int axisId, bool)
A Widget which contains a scale.
int startBorderDist() const
QwtText title() const
const QwtScaleDraw * scaleDraw() const
int endBorderDist() const
void getBorderDistHint(int &start, int &end) const
Calculate a hint for the border distances.
int dimForLength(int length, const QFont &scaleFont) const
Find the minimum dimension for a given length. dim is the height, length the width seen in direction ...
virtual QSize minimumSizeHint() const override
int titleHeightForWidth(int width) const
Find the height of the title for a given width.
A class representing a text.
Definition qwt_text.h:52
@ PaintUsingTextFont
The text has an individual font.
Definition qwt_text.h:117
bool isEmpty() const
Definition qwt_text.cpp:739
void setFont(const QFont &)
Definition qwt_text.cpp:329
double heightForWidth(double width) const
Definition qwt_text.cpp:522
A Widget which displays a QwtText.
virtual int heightForWidth(int) const override
const QwtText & text() const
Return the text.
bool isYAxis(int axisPos)
Definition qwt_axis.h:57
@ YRight
Y axis right of the canvas.
Definition qwt_axis.h:27
@ XTop
X axis above the canvas.
Definition qwt_axis.h:33
@ XBottom
X axis below the canvas.
Definition qwt_axis.h:30
@ YLeft
Y axis left of the canvas.
Definition qwt_axis.h:24
bool isValid(int axisPos)
Definition qwt_axis.h:45
bool isXAxis(int axisPos)
Definition qwt_axis.h:51