GCC Code Coverage Report


Directory: ./
File: plugins/pyqcustomplot/qcustomplot.cpp
Date: 2024-08-14 11:04:57
Exec Total Coverage
Lines: 0 9350 0.0%
Branches: 0 14379 0.0%

Line Branch Exec Source
1 /***************************************************************************
2 ** **
3 ** QCustomPlot, an easy to use, modern plotting widget for Qt **
4 ** Copyright (C) 2011-2015 Emanuel Eichhammer **
5 ** **
6 ** This program is free software: you can redistribute it and/or modify **
7 ** it under the terms of the GNU General Public License as published by **
8 ** the Free Software Foundation, either version 3 of the License, or **
9 ** (at your option) any later version. **
10 ** **
11 ** This program is distributed in the hope that it will be useful, **
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of **
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
14 ** GNU General Public License for more details. **
15 ** **
16 ** You should have received a copy of the GNU General Public License **
17 ** along with this program. If not, see http://www.gnu.org/licenses/. **
18 ** **
19 ****************************************************************************
20 ** Author: Emanuel Eichhammer **
21 ** Website/Contact: http://www.qcustomplot.com/ **
22 ** Date: 22.12.15 **
23 ** Version: 1.3.2 **
24 ****************************************************************************/
25
26 #include "qcustomplot.h"
27
28 ////////////////////////////////////////////////////////////////////////////////////////////////////
29 //////////////////// QCPPainter
30 ////////////////////////////////////////////////////////////////////////////////////////////////////
31
32 /*! \class QCPPainter
33 \brief QPainter subclass used internally
34
35 This QPainter subclass is used to provide some extended functionality e.g. for
36 tweaking position consistency between antialiased and non-antialiased
37 painting. Further it provides workarounds for QPainter quirks.
38
39 \warning This class intentionally hides non-virtual functions of QPainter,
40 e.g. setPen, save and restore. So while it is possible to pass a QCPPainter
41 instance to a function that expects a QPainter pointer, some of the
42 workarounds and tweaks will be unavailable to the function (because it will
43 call the base class implementations of the functions actually hidden by
44 QCPPainter).
45 */
46
47 /*!
48 Creates a new QCPPainter instance and sets default values
49 */
50 QCPPainter::QCPPainter()
51 : QPainter(), mModes(pmDefault), mIsAntialiasing(false) {
52 // don't setRenderHint(QPainter::NonCosmeticDefautPen) here, because painter
53 // isn't active yet and a call to begin() will follow
54 }
55
56 /*!
57 Creates a new QCPPainter instance on the specified paint \a device and sets
58 default values. Just like the analogous QPainter constructor, begins painting
59 on \a device immediately.
60
61 Like \ref begin, this method sets QPainter::NonCosmeticDefaultPen in Qt
62 versions before Qt5.
63 */
64 QCPPainter::QCPPainter(QPaintDevice *device)
65 : QPainter(device), mModes(pmDefault), mIsAntialiasing(false) {
66 #if QT_VERSION < \
67 QT_VERSION_CHECK(5, 0, \
68 0) // before Qt5, default pens used to be cosmetic if
69 // NonCosmeticDefaultPen flag isn't set. So we set it
70 // to get consistency across Qt versions.
71 if (isActive()) setRenderHint(QPainter::NonCosmeticDefaultPen);
72 #endif
73 }
74
75 QCPPainter::~QCPPainter() {}
76
77 /*!
78 Sets the pen of the painter and applies certain fixes to it, depending on the
79 mode of this QCPPainter.
80
81 \note this function hides the non-virtual base class implementation.
82 */
83 void QCPPainter::setPen(const QPen &pen) {
84 QPainter::setPen(pen);
85 if (mModes.testFlag(pmNonCosmetic)) makeNonCosmetic();
86 }
87
88 /*! \overload
89
90 Sets the pen (by color) of the painter and applies certain fixes to it,
91 depending on the mode of this QCPPainter.
92
93 \note this function hides the non-virtual base class implementation.
94 */
95 void QCPPainter::setPen(const QColor &color) {
96 QPainter::setPen(color);
97 if (mModes.testFlag(pmNonCosmetic)) makeNonCosmetic();
98 }
99
100 /*! \overload
101
102 Sets the pen (by style) of the painter and applies certain fixes to it,
103 depending on the mode of this QCPPainter.
104
105 \note this function hides the non-virtual base class implementation.
106 */
107 void QCPPainter::setPen(Qt::PenStyle penStyle) {
108 QPainter::setPen(penStyle);
109 if (mModes.testFlag(pmNonCosmetic)) makeNonCosmetic();
110 }
111
112 /*! \overload
113
114 Works around a Qt bug introduced with Qt 4.8 which makes drawing QLineF
115 unpredictable when antialiasing is disabled. Thus when antialiasing is
116 disabled, it rounds the \a line to integer coordinates and then passes it to
117 the original drawLine.
118
119 \note this function hides the non-virtual base class implementation.
120 */
121 void QCPPainter::drawLine(const QLineF &line) {
122 if (mIsAntialiasing || mModes.testFlag(pmVectorized))
123 QPainter::drawLine(line);
124 else
125 QPainter::drawLine(line.toLine());
126 }
127
128 /*!
129 Sets whether painting uses antialiasing or not. Use this method instead of
130 using setRenderHint with QPainter::Antialiasing directly, as it allows
131 QCPPainter to regain pixel exactness between antialiased and non-antialiased
132 painting (Since Qt < 5.0 uses slightly different coordinate systems for
133 AA/Non-AA painting).
134 */
135 void QCPPainter::setAntialiasing(bool enabled) {
136 setRenderHint(QPainter::Antialiasing, enabled);
137 if (mIsAntialiasing != enabled) {
138 mIsAntialiasing = enabled;
139 if (!mModes.testFlag(pmVectorized)) // antialiasing half-pixel shift only
140 // needed for rasterized outputs
141 {
142 if (mIsAntialiasing)
143 translate(0.5, 0.5);
144 else
145 translate(-0.5, -0.5);
146 }
147 }
148 }
149
150 /*!
151 Sets the mode of the painter. This controls whether the painter shall adjust
152 its fixes/workarounds optimized for certain output devices.
153 */
154 void QCPPainter::setModes(QCPPainter::PainterModes modes) { mModes = modes; }
155
156 /*!
157 Sets the QPainter::NonCosmeticDefaultPen in Qt versions before Qt5 after
158 beginning painting on \a device. This is necessary to get cosmetic pen
159 consistency across Qt versions, because since Qt5, all pens are non-cosmetic
160 by default, and in Qt4 this render hint must be set to get that behaviour.
161
162 The Constructor \ref QCPPainter(QPaintDevice *device) which directly starts
163 painting also sets the render hint as appropriate.
164
165 \note this function hides the non-virtual base class implementation.
166 */
167 bool QCPPainter::begin(QPaintDevice *device) {
168 bool result = QPainter::begin(device);
169 #if QT_VERSION < \
170 QT_VERSION_CHECK(5, 0, \
171 0) // before Qt5, default pens used to be cosmetic if
172 // NonCosmeticDefaultPen flag isn't set. So we set it
173 // to get consistency across Qt versions.
174 if (result) setRenderHint(QPainter::NonCosmeticDefaultPen);
175 #endif
176 return result;
177 }
178
179 /*! \overload
180
181 Sets the mode of the painter. This controls whether the painter shall adjust
182 its fixes/workarounds optimized for certain output devices.
183 */
184 void QCPPainter::setMode(QCPPainter::PainterMode mode, bool enabled) {
185 if (!enabled && mModes.testFlag(mode))
186 mModes &= ~mode;
187 else if (enabled && !mModes.testFlag(mode))
188 mModes |= mode;
189 }
190
191 /*!
192 Saves the painter (see QPainter::save). Since QCPPainter adds some new
193 internal state to QPainter, the save/restore functions are reimplemented to
194 also save/restore those members.
195
196 \note this function hides the non-virtual base class implementation.
197
198 \see restore
199 */
200 void QCPPainter::save() {
201 mAntialiasingStack.push(mIsAntialiasing);
202 QPainter::save();
203 }
204
205 /*!
206 Restores the painter (see QPainter::restore). Since QCPPainter adds some new
207 internal state to QPainter, the save/restore functions are reimplemented to
208 also save/restore those members.
209
210 \note this function hides the non-virtual base class implementation.
211
212 \see save
213 */
214 void QCPPainter::restore() {
215 if (!mAntialiasingStack.isEmpty())
216 mIsAntialiasing = mAntialiasingStack.pop();
217 else
218 qDebug() << Q_FUNC_INFO << "Unbalanced save/restore";
219 QPainter::restore();
220 }
221
222 /*!
223 Changes the pen width to 1 if it currently is 0. This function is called in
224 the \ref setPen overrides when the \ref pmNonCosmetic mode is set.
225 */
226 void QCPPainter::makeNonCosmetic() {
227 if (qFuzzyIsNull(pen().widthF())) {
228 QPen p = pen();
229 p.setWidth(1);
230 QPainter::setPen(p);
231 }
232 }
233
234 ////////////////////////////////////////////////////////////////////////////////////////////////////
235 //////////////////// QCPScatterStyle
236 ////////////////////////////////////////////////////////////////////////////////////////////////////
237
238 /*! \class QCPScatterStyle
239 \brief Represents the visual appearance of scatter points
240
241 This class holds information about shape, color and size of scatter points. In
242 plottables like QCPGraph it is used to store how scatter points shall be
243 drawn. For example, \ref QCPGraph::setScatterStyle takes a QCPScatterStyle
244 instance.
245
246 A scatter style consists of a shape (\ref setShape), a line color (\ref
247 setPen) and possibly a fill (\ref setBrush), if the shape provides a fillable
248 area. Further, the size of the shape can be controlled with \ref setSize.
249
250 \section QCPScatterStyle-defining Specifying a scatter style
251
252 You can set all these configurations either by calling the respective
253 functions on an instance: \snippet
254 documentation/doc-code-snippets/mainwindow.cpp qcpscatterstyle-creation-1
255
256 Or you can use one of the various constructors that take different parameter
257 combinations, making it easy to specify a scatter style in a single call, like
258 so: \snippet documentation/doc-code-snippets/mainwindow.cpp
259 qcpscatterstyle-creation-2
260
261 \section QCPScatterStyle-undefinedpen Leaving the color/pen up to the
262 plottable
263
264 There are two constructors which leave the pen undefined: \ref
265 QCPScatterStyle() and \ref QCPScatterStyle(ScatterShape shape, double size).
266 If those constructors are used, a call to \ref isPenDefined will return false.
267 It leads to scatter points that inherit the pen from the plottable that uses
268 the scatter style. Thus, if such a scatter style is passed to QCPGraph, the
269 line color of the graph (\ref QCPGraph::setPen) will be used by the scatter
270 points. This makes it very convenient to set up typical scatter settings:
271
272 \snippet documentation/doc-code-snippets/mainwindow.cpp
273 qcpscatterstyle-shortcreation
274
275 Notice that it wasn't even necessary to explicitly call a QCPScatterStyle
276 constructor. This works because QCPScatterStyle provides a constructor that
277 can transform a \ref ScatterShape directly into a QCPScatterStyle instance
278 (that's the \ref QCPScatterStyle(ScatterShape shape, double size) constructor
279 with a default for \a size). In those cases, C++ allows directly supplying a
280 \ref ScatterShape, where actually a QCPScatterStyle is expected.
281
282 \section QCPScatterStyle-custompath-and-pixmap Custom shapes and pixmaps
283
284 QCPScatterStyle supports drawing custom shapes and arbitrary pixmaps as
285 scatter points.
286
287 For custom shapes, you can provide a QPainterPath with the desired shape to
288 the \ref setCustomPath function or call the constructor that takes a painter
289 path. The scatter shape will automatically be set to \ref ssCustom.
290
291 For pixmaps, you call \ref setPixmap with the desired QPixmap. Alternatively
292 you can use the constructor that takes a QPixmap. The scatter shape will
293 automatically be set to \ref ssPixmap. Note that \ref setSize does not
294 influence the appearance of the pixmap.
295 */
296
297 /* start documentation of inline functions */
298
299 /*! \fn bool QCPScatterStyle::isNone() const
300
301 Returns whether the scatter shape is \ref ssNone.
302
303 \see setShape
304 */
305
306 /*! \fn bool QCPScatterStyle::isPenDefined() const
307
308 Returns whether a pen has been defined for this scatter style.
309
310 The pen is undefined if a constructor is called that does not carry \a pen as
311 parameter. Those are \ref QCPScatterStyle() and \ref
312 QCPScatterStyle(ScatterShape shape, double size). If the pen is left
313 undefined, the scatter color will be inherited from the plottable that uses
314 this scatter style.
315
316 \see setPen
317 */
318
319 /* end documentation of inline functions */
320
321 /*!
322 Creates a new QCPScatterStyle instance with size set to 6. No shape, pen or
323 brush is defined.
324
325 Since the pen is undefined (\ref isPenDefined returns false), the scatter
326 color will be inherited from the plottable that uses this scatter style.
327 */
328 QCPScatterStyle::QCPScatterStyle()
329 : mSize(6),
330 mShape(ssNone),
331 mPen(Qt::NoPen),
332 mBrush(Qt::NoBrush),
333 mPenDefined(false) {}
334
335 /*!
336 Creates a new QCPScatterStyle instance with shape set to \a shape and size to
337 \a size. No pen or brush is defined.
338
339 Since the pen is undefined (\ref isPenDefined returns false), the scatter
340 color will be inherited from the plottable that uses this scatter style.
341 */
342 QCPScatterStyle::QCPScatterStyle(ScatterShape shape, double size)
343 : mSize(size),
344 mShape(shape),
345 mPen(Qt::NoPen),
346 mBrush(Qt::NoBrush),
347 mPenDefined(false) {}
348
349 /*!
350 Creates a new QCPScatterStyle instance with shape set to \a shape, the pen
351 color set to \a color, and size to \a size. No brush is defined, i.e. the
352 scatter point will not be filled.
353 */
354 QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QColor &color,
355 double size)
356 : mSize(size),
357 mShape(shape),
358 mPen(QPen(color)),
359 mBrush(Qt::NoBrush),
360 mPenDefined(true) {}
361
362 /*!
363 Creates a new QCPScatterStyle instance with shape set to \a shape, the pen
364 color set to \a color, the brush color to \a fill (with a solid pattern), and
365 size to \a size.
366 */
367 QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QColor &color,
368 const QColor &fill, double size)
369 : mSize(size),
370 mShape(shape),
371 mPen(QPen(color)),
372 mBrush(QBrush(fill)),
373 mPenDefined(true) {}
374
375 /*!
376 Creates a new QCPScatterStyle instance with shape set to \a shape, the pen set
377 to \a pen, the brush to \a brush, and size to \a size.
378
379 \warning In some cases it might be tempting to directly use a pen style like
380 <tt>Qt::NoPen</tt> as \a pen and a color like <tt>Qt::blue</tt> as \a brush.
381 Notice however, that the corresponding call\n
382 <tt>QCPScatterStyle(QCPScatterShape::ssCircle, Qt::NoPen, Qt::blue, 5)</tt>\n
383 doesn't necessarily lead C++ to use this constructor in some cases, but might
384 mistake <tt>Qt::NoPen</tt> for a QColor and use the \ref
385 QCPScatterStyle(ScatterShape shape, const QColor &color, const QColor &fill,
386 double size) constructor instead (which will lead to an unexpected look of the
387 scatter points). To prevent this, be more explicit with the parameter types.
388 For example, use <tt>QBrush(Qt::blue)</tt> instead of just <tt>Qt::blue</tt>,
389 to clearly point out to the compiler that this constructor is wanted.
390 */
391 QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QPen &pen,
392 const QBrush &brush, double size)
393 : mSize(size),
394 mShape(shape),
395 mPen(pen),
396 mBrush(brush),
397 mPenDefined(pen.style() != Qt::NoPen) {}
398
399 /*!
400 Creates a new QCPScatterStyle instance which will show the specified \a
401 pixmap. The scatter shape is set to \ref ssPixmap.
402 */
403 QCPScatterStyle::QCPScatterStyle(const QPixmap &pixmap)
404 : mSize(5),
405 mShape(ssPixmap),
406 mPen(Qt::NoPen),
407 mBrush(Qt::NoBrush),
408 mPixmap(pixmap),
409 mPenDefined(false) {}
410
411 /*!
412 Creates a new QCPScatterStyle instance with a custom shape that is defined via
413 \a customPath. The scatter shape is set to \ref ssCustom.
414
415 The custom shape line will be drawn with \a pen and filled with \a brush. The
416 size has a slightly different meaning than for built-in scatter points: The
417 custom path will be drawn scaled by a factor of \a size/6.0. Since the default
418 \a size is 6, the custom path will appear at a its natural size by default. To
419 double the size of the path for example, set \a size to 12.
420 */
421 QCPScatterStyle::QCPScatterStyle(const QPainterPath &customPath,
422 const QPen &pen, const QBrush &brush,
423 double size)
424 : mSize(size),
425 mShape(ssCustom),
426 mPen(pen),
427 mBrush(brush),
428 mCustomPath(customPath),
429 mPenDefined(pen.style() != Qt::NoPen) {}
430
431 /*!
432 Sets the size (pixel diameter) of the drawn scatter points to \a size.
433
434 \see setShape
435 */
436 void QCPScatterStyle::setSize(double size) { mSize = size; }
437
438 /*!
439 Sets the shape to \a shape.
440
441 Note that the calls \ref setPixmap and \ref setCustomPath automatically set
442 the shape to \ref ssPixmap and \ref ssCustom, respectively.
443
444 \see setSize
445 */
446 void QCPScatterStyle::setShape(QCPScatterStyle::ScatterShape shape) {
447 mShape = shape;
448 }
449
450 /*!
451 Sets the pen that will be used to draw scatter points to \a pen.
452
453 If the pen was previously undefined (see \ref isPenDefined), the pen is
454 considered defined after a call to this function, even if \a pen is
455 <tt>Qt::NoPen</tt>.
456
457 \see setBrush
458 */
459 void QCPScatterStyle::setPen(const QPen &pen) {
460 mPenDefined = true;
461 mPen = pen;
462 }
463
464 /*!
465 Sets the brush that will be used to fill scatter points to \a brush. Note that
466 not all scatter shapes have fillable areas. For example, \ref ssPlus does not
467 while \ref ssCircle does.
468
469 \see setPen
470 */
471 void QCPScatterStyle::setBrush(const QBrush &brush) { mBrush = brush; }
472
473 /*!
474 Sets the pixmap that will be drawn as scatter point to \a pixmap.
475
476 Note that \ref setSize does not influence the appearance of the pixmap.
477
478 The scatter shape is automatically set to \ref ssPixmap.
479 */
480 void QCPScatterStyle::setPixmap(const QPixmap &pixmap) {
481 setShape(ssPixmap);
482 mPixmap = pixmap;
483 }
484
485 /*!
486 Sets the custom shape that will be drawn as scatter point to \a customPath.
487
488 The scatter shape is automatically set to \ref ssCustom.
489 */
490 void QCPScatterStyle::setCustomPath(const QPainterPath &customPath) {
491 setShape(ssCustom);
492 mCustomPath = customPath;
493 }
494
495 /*!
496 Applies the pen and the brush of this scatter style to \a painter. If this
497 scatter style has an undefined pen (\ref isPenDefined), sets the pen of \a
498 painter to \a defaultPen instead.
499
500 This function is used by plottables (or any class that wants to draw scatters)
501 just before a number of scatters with this style shall be drawn with the \a
502 painter.
503
504 \see drawShape
505 */
506 void QCPScatterStyle::applyTo(QCPPainter *painter,
507 const QPen &defaultPen) const {
508 painter->setPen(mPenDefined ? mPen : defaultPen);
509 painter->setBrush(mBrush);
510 }
511
512 /*!
513 Draws the scatter shape with \a painter at position \a pos.
514
515 This function does not modify the pen or the brush on the painter, as \ref
516 applyTo is meant to be called before scatter points are drawn with \ref
517 drawShape.
518
519 \see applyTo
520 */
521 void QCPScatterStyle::drawShape(QCPPainter *painter, QPointF pos) const {
522 drawShape(painter, pos.x(), pos.y());
523 }
524
525 /*! \overload
526 Draws the scatter shape with \a painter at position \a x and \a y.
527 */
528 void QCPScatterStyle::drawShape(QCPPainter *painter, double x, double y) const {
529 double w = mSize / 2.0;
530 switch (mShape) {
531 case ssNone:
532 break;
533 case ssDot: {
534 painter->drawLine(QPointF(x, y), QPointF(x + 0.0001, y));
535 break;
536 }
537 case ssCross: {
538 painter->drawLine(QLineF(x - w, y - w, x + w, y + w));
539 painter->drawLine(QLineF(x - w, y + w, x + w, y - w));
540 break;
541 }
542 case ssPlus: {
543 painter->drawLine(QLineF(x - w, y, x + w, y));
544 painter->drawLine(QLineF(x, y + w, x, y - w));
545 break;
546 }
547 case ssCircle: {
548 painter->drawEllipse(QPointF(x, y), w, w);
549 break;
550 }
551 case ssDisc: {
552 QBrush b = painter->brush();
553 painter->setBrush(painter->pen().color());
554 painter->drawEllipse(QPointF(x, y), w, w);
555 painter->setBrush(b);
556 break;
557 }
558 case ssSquare: {
559 painter->drawRect(QRectF(x - w, y - w, mSize, mSize));
560 break;
561 }
562 case ssDiamond: {
563 painter->drawLine(QLineF(x - w, y, x, y - w));
564 painter->drawLine(QLineF(x, y - w, x + w, y));
565 painter->drawLine(QLineF(x + w, y, x, y + w));
566 painter->drawLine(QLineF(x, y + w, x - w, y));
567 break;
568 }
569 case ssStar: {
570 painter->drawLine(QLineF(x - w, y, x + w, y));
571 painter->drawLine(QLineF(x, y + w, x, y - w));
572 painter->drawLine(
573 QLineF(x - w * 0.707, y - w * 0.707, x + w * 0.707, y + w * 0.707));
574 painter->drawLine(
575 QLineF(x - w * 0.707, y + w * 0.707, x + w * 0.707, y - w * 0.707));
576 break;
577 }
578 case ssTriangle: {
579 painter->drawLine(QLineF(x - w, y + 0.755 * w, x + w, y + 0.755 * w));
580 painter->drawLine(QLineF(x + w, y + 0.755 * w, x, y - 0.977 * w));
581 painter->drawLine(QLineF(x, y - 0.977 * w, x - w, y + 0.755 * w));
582 break;
583 }
584 case ssTriangleInverted: {
585 painter->drawLine(QLineF(x - w, y - 0.755 * w, x + w, y - 0.755 * w));
586 painter->drawLine(QLineF(x + w, y - 0.755 * w, x, y + 0.977 * w));
587 painter->drawLine(QLineF(x, y + 0.977 * w, x - w, y - 0.755 * w));
588 break;
589 }
590 case ssCrossSquare: {
591 painter->drawLine(QLineF(x - w, y - w, x + w * 0.95, y + w * 0.95));
592 painter->drawLine(QLineF(x - w, y + w * 0.95, x + w * 0.95, y - w));
593 painter->drawRect(QRectF(x - w, y - w, mSize, mSize));
594 break;
595 }
596 case ssPlusSquare: {
597 painter->drawLine(QLineF(x - w, y, x + w * 0.95, y));
598 painter->drawLine(QLineF(x, y + w, x, y - w));
599 painter->drawRect(QRectF(x - w, y - w, mSize, mSize));
600 break;
601 }
602 case ssCrossCircle: {
603 painter->drawLine(
604 QLineF(x - w * 0.707, y - w * 0.707, x + w * 0.670, y + w * 0.670));
605 painter->drawLine(
606 QLineF(x - w * 0.707, y + w * 0.670, x + w * 0.670, y - w * 0.707));
607 painter->drawEllipse(QPointF(x, y), w, w);
608 break;
609 }
610 case ssPlusCircle: {
611 painter->drawLine(QLineF(x - w, y, x + w, y));
612 painter->drawLine(QLineF(x, y + w, x, y - w));
613 painter->drawEllipse(QPointF(x, y), w, w);
614 break;
615 }
616 case ssPeace: {
617 painter->drawLine(QLineF(x, y - w, x, y + w));
618 painter->drawLine(QLineF(x, y, x - w * 0.707, y + w * 0.707));
619 painter->drawLine(QLineF(x, y, x + w * 0.707, y + w * 0.707));
620 painter->drawEllipse(QPointF(x, y), w, w);
621 break;
622 }
623 case ssPixmap: {
624 painter->drawPixmap(x - mPixmap.width() * 0.5, y - mPixmap.height() * 0.5,
625 mPixmap);
626 break;
627 }
628 case ssCustom: {
629 QTransform oldTransform = painter->transform();
630 painter->translate(x, y);
631 painter->scale(mSize / 6.0, mSize / 6.0);
632 painter->drawPath(mCustomPath);
633 painter->setTransform(oldTransform);
634 break;
635 }
636 }
637 }
638
639 ////////////////////////////////////////////////////////////////////////////////////////////////////
640 //////////////////// QCPLayer
641 ////////////////////////////////////////////////////////////////////////////////////////////////////
642
643 /*! \class QCPLayer
644 \brief A layer that may contain objects, to control the rendering order
645
646 The Layering system of QCustomPlot is the mechanism to control the rendering
647 order of the elements inside the plot.
648
649 It is based on the two classes QCPLayer and QCPLayerable. QCustomPlot holds an
650 ordered list of one or more instances of QCPLayer (see QCustomPlot::addLayer,
651 QCustomPlot::layer, QCustomPlot::moveLayer, etc.). When replotting,
652 QCustomPlot goes through the list of layers bottom to top and successively
653 draws the layerables of the layers.
654
655 A QCPLayer contains an ordered list of QCPLayerable instances. QCPLayerable is
656 an abstract base class from which almost all visible objects derive, like
657 axes, grids, graphs, items, etc.
658
659 Initially, QCustomPlot has five layers: "background", "grid", "main", "axes"
660 and "legend" (in that order). The top two layers "axes" and "legend" contain
661 the default axes and legend, so they will be drawn on top. In the middle,
662 there is the "main" layer. It is initially empty and set as the current layer
663 (see QCustomPlot::setCurrentLayer). This means, all new plottables, items etc.
664 are created on this layer by default. Then comes the "grid" layer which
665 contains the QCPGrid instances (which belong tightly to QCPAxis, see \ref
666 QCPAxis::grid). The Axis rect background shall be drawn behind everything
667 else, thus the default QCPAxisRect instance is placed on the "background"
668 layer. Of course, the layer affiliation of the individual objects can be
669 changed as required (\ref QCPLayerable::setLayer).
670
671 Controlling the ordering of objects is easy: Create a new layer in the
672 position you want it to be, e.g. above "main", with QCustomPlot::addLayer.
673 Then set the current layer with QCustomPlot::setCurrentLayer to that new layer
674 and finally create the objects normally. They will be placed on the new layer
675 automatically, due to the current layer setting. Alternatively you could have
676 also ignored the current layer setting and just moved the objects with
677 QCPLayerable::setLayer to the desired layer after creating them.
678
679 It is also possible to move whole layers. For example, If you want the grid to
680 be shown in front of all plottables/items on the "main" layer, just move it
681 above "main" with QCustomPlot::moveLayer.
682
683 The rendering order within one layer is simply by order of creation or
684 insertion. The item created last (or added last to the layer), is drawn on top
685 of all other objects on that layer.
686
687 When a layer is deleted, the objects on it are not deleted with it, but fall
688 on the layer below the deleted layer, see QCustomPlot::removeLayer.
689 */
690
691 /* start documentation of inline functions */
692
693 /*! \fn QList<QCPLayerable*> QCPLayer::children() const
694
695 Returns a list of all layerables on this layer. The order corresponds to the
696 rendering order: layerables with higher indices are drawn above layerables
697 with lower indices.
698 */
699
700 /*! \fn int QCPLayer::index() const
701
702 Returns the index this layer has in the QCustomPlot. The index is the integer
703 number by which this layer can be accessed via \ref QCustomPlot::layer.
704
705 Layers with higher indices will be drawn above layers with lower indices.
706 */
707
708 /* end documentation of inline functions */
709
710 /*!
711 Creates a new QCPLayer instance.
712
713 Normally you shouldn't directly instantiate layers, use \ref
714 QCustomPlot::addLayer instead.
715
716 \warning It is not checked that \a layerName is actually a unique layer name
717 in \a parentPlot. This check is only performed by \ref QCustomPlot::addLayer.
718 */
719 QCPLayer::QCPLayer(QCustomPlot *parentPlot, const QString &layerName)
720 : QObject(parentPlot),
721 mParentPlot(parentPlot),
722 mName(layerName),
723 mIndex(-1), // will be set to a proper value by the QCustomPlot layer
724 // creation function
725 mVisible(true) {
726 // Note: no need to make sure layerName is unique, because layer
727 // management is done with QCustomPlot functions.
728 }
729
730 QCPLayer::~QCPLayer() {
731 // If child layerables are still on this layer, detach them, so they don't try
732 // to reach back to this then invalid layer once they get deleted/moved
733 // themselves. This only happens when layers are deleted directly, like in the
734 // QCustomPlot destructor. (The regular layer removal procedure for the user
735 // is to call QCustomPlot::removeLayer, which moves all layerables off this
736 // layer before deleting it.)
737
738 while (!mChildren.isEmpty())
739 mChildren.last()->setLayer(
740 0); // removes itself from mChildren via removeChild()
741
742 if (mParentPlot->currentLayer() == this)
743 qDebug() << Q_FUNC_INFO
744 << "The parent plot's mCurrentLayer will be a dangling pointer. "
745 "Should have been set to a valid layer or 0 beforehand.";
746 }
747
748 /*!
749 Sets whether this layer is visible or not. If \a visible is set to false, all
750 layerables on this layer will be invisible.
751
752 This function doesn't change the visibility property of the layerables (\ref
753 QCPLayerable::setVisible), but the \ref QCPLayerable::realVisibility of each
754 layerable takes the visibility of the parent layer into account.
755 */
756 void QCPLayer::setVisible(bool visible) { mVisible = visible; }
757
758 /*! \internal
759
760 Adds the \a layerable to the list of this layer. If \a prepend is set to true,
761 the layerable will be prepended to the list, i.e. be drawn beneath the other
762 layerables already in the list.
763
764 This function does not change the \a mLayer member of \a layerable to this
765 layer. (Use QCPLayerable::setLayer to change the layer of an object, not this
766 function.)
767
768 \see removeChild
769 */
770 void QCPLayer::addChild(QCPLayerable *layerable, bool prepend) {
771 if (!mChildren.contains(layerable)) {
772 if (prepend)
773 mChildren.prepend(layerable);
774 else
775 mChildren.append(layerable);
776 } else
777 qDebug() << Q_FUNC_INFO << "layerable is already child of this layer"
778 << reinterpret_cast<quintptr>(layerable);
779 }
780
781 /*! \internal
782
783 Removes the \a layerable from the list of this layer.
784
785 This function does not change the \a mLayer member of \a layerable. (Use
786 QCPLayerable::setLayer to change the layer of an object, not this function.)
787
788 \see addChild
789 */
790 void QCPLayer::removeChild(QCPLayerable *layerable) {
791 if (!mChildren.removeOne(layerable))
792 qDebug() << Q_FUNC_INFO << "layerable is not child of this layer"
793 << reinterpret_cast<quintptr>(layerable);
794 }
795
796 ////////////////////////////////////////////////////////////////////////////////////////////////////
797 //////////////////// QCPLayerable
798 ////////////////////////////////////////////////////////////////////////////////////////////////////
799
800 /*! \class QCPLayerable
801 \brief Base class for all drawable objects
802
803 This is the abstract base class most visible objects derive from, e.g.
804 plottables, axes, grid etc.
805
806 Every layerable is on a layer (QCPLayer) which allows controlling the
807 rendering order by stacking the layers accordingly.
808
809 For details about the layering mechanism, see the QCPLayer documentation.
810 */
811
812 /* start documentation of inline functions */
813
814 /*! \fn QCPLayerable *QCPLayerable::parentLayerable() const
815
816 Returns the parent layerable of this layerable. The parent layerable is used
817 to provide visibility hierarchies in conjunction with the method \ref
818 realVisibility. This way, layerables only get drawn if their parent layerables
819 are visible, too.
820
821 Note that a parent layerable is not necessarily also the QObject parent for
822 memory management. Further, a layerable doesn't always have a parent
823 layerable, so this function may return 0.
824
825 A parent layerable is set implicitly with when placed inside layout elements
826 and doesn't need to be set manually by the user.
827 */
828
829 /* end documentation of inline functions */
830 /* start documentation of pure virtual functions */
831
832 /*! \fn virtual void QCPLayerable::applyDefaultAntialiasingHint(QCPPainter
833 *painter) const = 0 \internal
834
835 This function applies the default antialiasing setting to the specified \a
836 painter, using the function \ref applyAntialiasingHint. It is the antialiasing
837 state the painter is put in, when \ref draw is called on the layerable. If the
838 layerable has multiple entities whose antialiasing setting may be specified
839 individually, this function should set the antialiasing state of the most
840 prominent entity. In this case however, the \ref draw function usually calls
841 the specialized versions of this function before drawing each entity,
842 effectively overriding the setting of the default antialiasing hint.
843
844 <b>First example:</b> QCPGraph has multiple entities that have an antialiasing
845 setting: The graph line, fills, scatters and error bars. Those can be
846 configured via QCPGraph::setAntialiased, QCPGraph::setAntialiasedFill,
847 QCPGraph::setAntialiasedScatters etc. Consequently, there isn't only the
848 QCPGraph::applyDefaultAntialiasingHint function (which corresponds to the
849 graph line's antialiasing), but specialized ones like
850 QCPGraph::applyFillAntialiasingHint and
851 QCPGraph::applyScattersAntialiasingHint. So before drawing one of those
852 entities, QCPGraph::draw calls the respective specialized
853 applyAntialiasingHint function.
854
855 <b>Second example:</b> QCPItemLine consists only of a line so there is only
856 one antialiasing setting which can be controlled with
857 QCPItemLine::setAntialiased. (This function is inherited by all layerables.
858 The specialized functions, as seen on QCPGraph, must be added explicitly to
859 the respective layerable subclass.) Consequently it only has the normal
860 QCPItemLine::applyDefaultAntialiasingHint. The \ref QCPItemLine::draw function
861 doesn't need to care about setting any antialiasing states, because the
862 default antialiasing hint is already set on the painter when the \ref draw
863 function is called, and that's the state it wants to draw the line with.
864 */
865
866 /*! \fn virtual void QCPLayerable::draw(QCPPainter *painter) const = 0
867 \internal
868
869 This function draws the layerable with the specified \a painter. It is only
870 called by QCustomPlot, if the layerable is visible (\ref setVisible).
871
872 Before this function is called, the painter's antialiasing state is set via
873 \ref applyDefaultAntialiasingHint, see the documentation there. Further, the
874 clipping rectangle was set to \ref clipRect.
875 */
876
877 /* end documentation of pure virtual functions */
878 /* start documentation of signals */
879
880 /*! \fn void QCPLayerable::layerChanged(QCPLayer *newLayer);
881
882 This signal is emitted when the layer of this layerable changes, i.e. this
883 layerable is moved to a different layer.
884
885 \see setLayer
886 */
887
888 /* end documentation of signals */
889
890 /*!
891 Creates a new QCPLayerable instance.
892
893 Since QCPLayerable is an abstract base class, it can't be instantiated
894 directly. Use one of the derived classes.
895
896 If \a plot is provided, it automatically places itself on the layer named \a
897 targetLayer. If \a targetLayer is an empty string, it places itself on the
898 current layer of the plot (see \ref QCustomPlot::setCurrentLayer).
899
900 It is possible to provide 0 as \a plot. In that case, you should assign a
901 parent plot at a later time with \ref initializeParentPlot.
902
903 The layerable's parent layerable is set to \a parentLayerable, if provided.
904 Direct layerable parents are mainly used to control visibility in a hierarchy
905 of layerables. This means a layerable is only drawn, if all its ancestor
906 layerables are also visible. Note that \a parentLayerable does not become the
907 QObject-parent (for memory management) of this layerable, \a plot does. It is
908 not uncommon to set the QObject-parent to something else in the constructors
909 of QCPLayerable subclasses, to guarantee a working destruction hierarchy.
910 */
911 QCPLayerable::QCPLayerable(QCustomPlot *plot, QString targetLayer,
912 QCPLayerable *parentLayerable)
913 : QObject(plot),
914 mVisible(true),
915 mParentPlot(plot),
916 mParentLayerable(parentLayerable),
917 mLayer(0),
918 mAntialiased(true) {
919 if (mParentPlot) {
920 if (targetLayer.isEmpty())
921 setLayer(mParentPlot->currentLayer());
922 else if (!setLayer(targetLayer))
923 qDebug() << Q_FUNC_INFO << "setting QCPlayerable initial layer to"
924 << targetLayer << "failed.";
925 }
926 }
927
928 QCPLayerable::~QCPLayerable() {
929 if (mLayer) {
930 mLayer->removeChild(this);
931 mLayer = 0;
932 }
933 }
934
935 /*!
936 Sets the visibility of this layerable object. If an object is not visible, it
937 will not be drawn on the QCustomPlot surface, and user interaction with it
938 (e.g. click and selection) is not possible.
939 */
940 void QCPLayerable::setVisible(bool on) { mVisible = on; }
941
942 /*!
943 Sets the \a layer of this layerable object. The object will be placed on top
944 of the other objects already on \a layer.
945
946 If \a layer is 0, this layerable will not be on any layer and thus not appear
947 in the plot (or interact/receive events).
948
949 Returns true if the layer of this layerable was successfully changed to \a
950 layer.
951 */
952 bool QCPLayerable::setLayer(QCPLayer *layer) {
953 return moveToLayer(layer, false);
954 }
955
956 /*! \overload
957 Sets the layer of this layerable object by name
958
959 Returns true on success, i.e. if \a layerName is a valid layer name.
960 */
961 bool QCPLayerable::setLayer(const QString &layerName) {
962 if (!mParentPlot) {
963 qDebug() << Q_FUNC_INFO << "no parent QCustomPlot set";
964 return false;
965 }
966 if (QCPLayer *layer = mParentPlot->layer(layerName)) {
967 return setLayer(layer);
968 } else {
969 qDebug() << Q_FUNC_INFO << "there is no layer with name" << layerName;
970 return false;
971 }
972 }
973
974 /*!
975 Sets whether this object will be drawn antialiased or not.
976
977 Note that antialiasing settings may be overridden by
978 QCustomPlot::setAntialiasedElements and
979 QCustomPlot::setNotAntialiasedElements.
980 */
981 void QCPLayerable::setAntialiased(bool enabled) { mAntialiased = enabled; }
982
983 /*!
984 Returns whether this layerable is visible, taking the visibility of the
985 layerable parent and the visibility of the layer this layerable is on into
986 account. This is the method that is consulted to decide whether a layerable
987 shall be drawn or not.
988
989 If this layerable has a direct layerable parent (usually set via hierarchies
990 implemented in subclasses, like in the case of QCPLayoutElement), this
991 function returns true only if this layerable has its visibility set to true
992 and the parent layerable's \ref realVisibility returns true.
993
994 If this layerable doesn't have a direct layerable parent, returns the state of
995 this layerable's visibility.
996 */
997 bool QCPLayerable::realVisibility() const {
998 return mVisible && (!mLayer || mLayer->visible()) &&
999 (!mParentLayerable || mParentLayerable.data()->realVisibility());
1000 }
1001
1002 /*!
1003 This function is used to decide whether a click hits a layerable object or
1004 not.
1005
1006 \a pos is a point in pixel coordinates on the QCustomPlot surface. This
1007 function returns the shortest pixel distance of this point to the object. If
1008 the object is either invisible or the distance couldn't be determined, -1.0 is
1009 returned. Further, if \a onlySelectable is true and the object is not
1010 selectable, -1.0 is returned, too.
1011
1012 If the object is represented not by single lines but by an area like a \ref
1013 QCPItemText or the bars of a \ref QCPBars plottable, a click inside the area
1014 should also be considered a hit. In these cases this function thus returns a
1015 constant value greater zero but still below the parent plot's selection
1016 tolerance. (typically the selectionTolerance multiplied by 0.99).
1017
1018 Providing a constant value for area objects allows selecting line objects even
1019 when they are obscured by such area objects, by clicking close to the lines
1020 (i.e. closer than 0.99*selectionTolerance).
1021
1022 The actual setting of the selection state is not done by this function. This
1023 is handled by the parent QCustomPlot when the mouseReleaseEvent occurs, and
1024 the finally selected object is notified via the selectEvent/deselectEvent
1025 methods.
1026
1027 \a details is an optional output parameter. Every layerable subclass may place
1028 any information in \a details. This information will be passed to \ref
1029 selectEvent when the parent QCustomPlot decides on the basis of this
1030 selectTest call, that the object was successfully selected. The subsequent
1031 call to \ref selectEvent will carry the \a details. This is useful for
1032 multi-part objects (like QCPAxis). This way, a possibly complex calculation to
1033 decide which part was clicked is only done once in \ref selectTest. The result
1034 (i.e. the actually clicked part) can then be placed in \a details. So in the
1035 subsequent \ref selectEvent, the decision which part was selected doesn't have
1036 to be done a second time for a single selection operation.
1037
1038 You may pass 0 as \a details to indicate that you are not interested in those
1039 selection details.
1040
1041 \see selectEvent, deselectEvent, QCustomPlot::setInteractions
1042 */
1043 double QCPLayerable::selectTest(const QPointF &pos, bool onlySelectable,
1044 QVariant *details) const {
1045 Q_UNUSED(pos)
1046 Q_UNUSED(onlySelectable)
1047 Q_UNUSED(details)
1048 return -1.0;
1049 }
1050
1051 /*! \internal
1052
1053 Sets the parent plot of this layerable. Use this function once to set the
1054 parent plot if you have passed 0 in the constructor. It can not be used to
1055 move a layerable from one QCustomPlot to another one.
1056
1057 Note that, unlike when passing a non-null parent plot in the constructor, this
1058 function does not make \a parentPlot the QObject-parent of this layerable. If
1059 you want this, call QObject::setParent(\a parentPlot) in addition to this
1060 function.
1061
1062 Further, you will probably want to set a layer (\ref setLayer) after calling
1063 this function, to make the layerable appear on the QCustomPlot.
1064
1065 The parent plot change will be propagated to subclasses via a call to \ref
1066 parentPlotInitialized so they can react accordingly (e.g. also initialize the
1067 parent plot of child layerables, like QCPLayout does).
1068 */
1069 void QCPLayerable::initializeParentPlot(QCustomPlot *parentPlot) {
1070 if (mParentPlot) {
1071 qDebug() << Q_FUNC_INFO << "called with mParentPlot already initialized";
1072 return;
1073 }
1074
1075 if (!parentPlot) qDebug() << Q_FUNC_INFO << "called with parentPlot zero";
1076
1077 mParentPlot = parentPlot;
1078 parentPlotInitialized(mParentPlot);
1079 }
1080
1081 /*! \internal
1082
1083 Sets the parent layerable of this layerable to \a parentLayerable. Note that
1084 \a parentLayerable does not become the QObject-parent (for memory management)
1085 of this layerable.
1086
1087 The parent layerable has influence on the return value of the \ref
1088 realVisibility method. Only layerables with a fully visible parent tree will
1089 return true for \ref realVisibility, and thus be drawn.
1090
1091 \see realVisibility
1092 */
1093 void QCPLayerable::setParentLayerable(QCPLayerable *parentLayerable) {
1094 mParentLayerable = parentLayerable;
1095 }
1096
1097 /*! \internal
1098
1099 Moves this layerable object to \a layer. If \a prepend is true, this object
1100 will be prepended to the new layer's list, i.e. it will be drawn below the
1101 objects already on the layer. If it is false, the object will be appended.
1102
1103 Returns true on success, i.e. if \a layer is a valid layer.
1104 */
1105 bool QCPLayerable::moveToLayer(QCPLayer *layer, bool prepend) {
1106 if (layer && !mParentPlot) {
1107 qDebug() << Q_FUNC_INFO << "no parent QCustomPlot set";
1108 return false;
1109 }
1110 if (layer && layer->parentPlot() != mParentPlot) {
1111 qDebug() << Q_FUNC_INFO << "layer" << layer->name()
1112 << "is not in same QCustomPlot as this layerable";
1113 return false;
1114 }
1115
1116 QCPLayer *oldLayer = mLayer;
1117 if (mLayer) mLayer->removeChild(this);
1118 mLayer = layer;
1119 if (mLayer) mLayer->addChild(this, prepend);
1120 if (mLayer != oldLayer) emit layerChanged(mLayer);
1121 return true;
1122 }
1123
1124 /*! \internal
1125
1126 Sets the QCPainter::setAntialiasing state on the provided \a painter,
1127 depending on the \a localAntialiased value as well as the overrides \ref
1128 QCustomPlot::setAntialiasedElements and \ref
1129 QCustomPlot::setNotAntialiasedElements. Which override enum this function
1130 takes into account is controlled via \a overrideElement.
1131 */
1132 void QCPLayerable::applyAntialiasingHint(
1133 QCPPainter *painter, bool localAntialiased,
1134 QCP::AntialiasedElement overrideElement) const {
1135 if (mParentPlot &&
1136 mParentPlot->notAntialiasedElements().testFlag(overrideElement))
1137 painter->setAntialiasing(false);
1138 else if (mParentPlot &&
1139 mParentPlot->antialiasedElements().testFlag(overrideElement))
1140 painter->setAntialiasing(true);
1141 else
1142 painter->setAntialiasing(localAntialiased);
1143 }
1144
1145 /*! \internal
1146
1147 This function is called by \ref initializeParentPlot, to allow subclasses to
1148 react on the setting of a parent plot. This is the case when 0 was passed as
1149 parent plot in the constructor, and the parent plot is set at a later time.
1150
1151 For example, QCPLayoutElement/QCPLayout hierarchies may be created
1152 independently of any QCustomPlot at first. When they are then added to a
1153 layout inside the QCustomPlot, the top level element of the hierarchy gets its
1154 parent plot initialized with \ref initializeParentPlot. To propagate the
1155 parent plot to all the children of the hierarchy, the top level element then
1156 uses this function to pass the parent plot on to its child elements.
1157
1158 The default implementation does nothing.
1159
1160 \see initializeParentPlot
1161 */
1162 void QCPLayerable::parentPlotInitialized(QCustomPlot *parentPlot) {
1163 Q_UNUSED(parentPlot)
1164 }
1165
1166 /*! \internal
1167
1168 Returns the selection category this layerable shall belong to. The selection
1169 category is used in conjunction with \ref QCustomPlot::setInteractions to
1170 control which objects are selectable and which aren't.
1171
1172 Subclasses that don't fit any of the normal \ref QCP::Interaction values can
1173 use \ref QCP::iSelectOther. This is what the default implementation returns.
1174
1175 \see QCustomPlot::setInteractions
1176 */
1177 QCP::Interaction QCPLayerable::selectionCategory() const {
1178 return QCP::iSelectOther;
1179 }
1180
1181 /*! \internal
1182
1183 Returns the clipping rectangle of this layerable object. By default, this is
1184 the viewport of the parent QCustomPlot. Specific subclasses may reimplement
1185 this function to provide different clipping rects.
1186
1187 The returned clipping rect is set on the painter before the draw function of
1188 the respective object is called.
1189 */
1190 QRect QCPLayerable::clipRect() const {
1191 if (mParentPlot)
1192 return mParentPlot->viewport();
1193 else
1194 return QRect();
1195 }
1196
1197 /*! \internal
1198
1199 This event is called when the layerable shall be selected, as a consequence of
1200 a click by the user. Subclasses should react to it by setting their selection
1201 state appropriately. The default implementation does nothing.
1202
1203 \a event is the mouse event that caused the selection. \a additive indicates,
1204 whether the user was holding the multi-select-modifier while performing the
1205 selection (see \ref QCustomPlot::setMultiSelectModifier). if \a additive is
1206 true, the selection state must be toggled (i.e. become selected when
1207 unselected and unselected when selected).
1208
1209 Every selectEvent is preceded by a call to \ref selectTest, which has returned
1210 positively (i.e. returned a value greater than 0 and less than the selection
1211 tolerance of the parent QCustomPlot). The \a details data you output from \ref
1212 selectTest is fed back via \a details here. You may use it to transport any
1213 kind of information from the selectTest to the possibly subsequent
1214 selectEvent. Usually \a details is used to transfer which part was clicked, if
1215 it is a layerable that has multiple individually selectable parts (like
1216 QCPAxis). This way selectEvent doesn't need to do the calculation again to
1217 find out which part was actually clicked.
1218
1219 \a selectionStateChanged is an output parameter. If the pointer is non-null,
1220 this function must set the value either to true or false, depending on whether
1221 the selection state of this layerable was actually changed. For layerables
1222 that only are selectable as a whole and not in parts, this is simple: if \a
1223 additive is true, \a selectionStateChanged must also be set to true, because
1224 the selection toggles. If \a additive is false, \a selectionStateChanged is
1225 only set to true, if the layerable was previously unselected and now is
1226 switched to the selected state.
1227
1228 \see selectTest, deselectEvent
1229 */
1230 void QCPLayerable::selectEvent(QMouseEvent *event, bool additive,
1231 const QVariant &details,
1232 bool *selectionStateChanged) {
1233 Q_UNUSED(event)
1234 Q_UNUSED(additive)
1235 Q_UNUSED(details)
1236 Q_UNUSED(selectionStateChanged)
1237 }
1238
1239 /*! \internal
1240
1241 This event is called when the layerable shall be deselected, either as
1242 consequence of a user interaction or a call to \ref QCustomPlot::deselectAll.
1243 Subclasses should react to it by unsetting their selection appropriately.
1244
1245 just as in \ref selectEvent, the output parameter \a selectionStateChanged (if
1246 non-null), must return true or false when the selection state of this
1247 layerable has changed or not changed, respectively.
1248
1249 \see selectTest, selectEvent
1250 */
1251 void QCPLayerable::deselectEvent(bool *selectionStateChanged) {
1252 Q_UNUSED(selectionStateChanged)
1253 }
1254
1255 ////////////////////////////////////////////////////////////////////////////////////////////////////
1256 //////////////////// QCPRange
1257 ////////////////////////////////////////////////////////////////////////////////////////////////////
1258 /*! \class QCPRange
1259 \brief Represents the range an axis is encompassing.
1260
1261 contains a \a lower and \a upper double value and provides convenience input,
1262 output and modification functions.
1263
1264 \see QCPAxis::setRange
1265 */
1266
1267 /*!
1268 Minimum range size (\a upper - \a lower) the range changing functions will
1269 accept. Smaller intervals would cause errors due to the 11-bit exponent of
1270 double precision numbers, corresponding to a minimum magnitude of roughly
1271 1e-308. \see validRange, maxRange
1272 */
1273 const double QCPRange::minRange = 1e-280;
1274
1275 /*!
1276 Maximum values (negative and positive) the range will accept in range-changing
1277 functions. Larger absolute values would cause errors due to the 11-bit
1278 exponent of double precision numbers, corresponding to a maximum magnitude of
1279 roughly 1e308. Since the number of planck-volumes in the entire visible
1280 universe is only ~1e183, this should be enough. \see validRange, minRange
1281 */
1282 const double QCPRange::maxRange = 1e250;
1283
1284 /*!
1285 Constructs a range with \a lower and \a upper set to zero.
1286 */
1287 QCPRange::QCPRange() : lower(0), upper(0) {}
1288
1289 /*! \overload
1290 Constructs a range with the specified \a lower and \a upper values.
1291 */
1292 QCPRange::QCPRange(double lower, double upper) : lower(lower), upper(upper) {
1293 normalize();
1294 }
1295
1296 /*!
1297 Returns the size of the range, i.e. \a upper-\a lower
1298 */
1299 double QCPRange::size() const { return upper - lower; }
1300
1301 /*!
1302 Returns the center of the range, i.e. (\a upper+\a lower)*0.5
1303 */
1304 double QCPRange::center() const { return (upper + lower) * 0.5; }
1305
1306 /*!
1307 Makes sure \a lower is numerically smaller than \a upper. If this is not the
1308 case, the values are swapped.
1309 */
1310 void QCPRange::normalize() {
1311 if (lower > upper) qSwap(lower, upper);
1312 }
1313
1314 /*!
1315 Expands this range such that \a otherRange is contained in the new range. It
1316 is assumed that both this range and \a otherRange are normalized (see \ref
1317 normalize).
1318
1319 If \a otherRange is already inside the current range, this function does
1320 nothing.
1321
1322 \see expanded
1323 */
1324 void QCPRange::expand(const QCPRange &otherRange) {
1325 if (lower > otherRange.lower) lower = otherRange.lower;
1326 if (upper < otherRange.upper) upper = otherRange.upper;
1327 }
1328
1329 /*!
1330 Returns an expanded range that contains this and \a otherRange. It is assumed
1331 that both this range and \a otherRange are normalized (see \ref normalize).
1332
1333 \see expand
1334 */
1335 QCPRange QCPRange::expanded(const QCPRange &otherRange) const {
1336 QCPRange result = *this;
1337 result.expand(otherRange);
1338 return result;
1339 }
1340
1341 /*!
1342 Returns a sanitized version of the range. Sanitized means for logarithmic
1343 scales, that the range won't span the positive and negative sign domain, i.e.
1344 contain zero. Further \a lower will always be numerically smaller (or equal)
1345 to \a upper.
1346
1347 If the original range does span positive and negative sign domains or contains
1348 zero, the returned range will try to approximate the original range as good as
1349 possible. If the positive interval of the original range is wider than the
1350 negative interval, the returned range will only contain the positive interval,
1351 with lower bound set to \a rangeFac or \a rangeFac *\a upper, whichever is
1352 closer to zero. Same procedure is used if the negative interval is wider than
1353 the positive interval, this time by changing the \a upper bound.
1354 */
1355 QCPRange QCPRange::sanitizedForLogScale() const {
1356 double rangeFac = 1e-3;
1357 QCPRange sanitizedRange(lower, upper);
1358 sanitizedRange.normalize();
1359 // can't have range spanning negative and positive values in log plot, so
1360 // change range to fix it
1361 // if (qFuzzyCompare(sanitizedRange.lower+1, 1) &&
1362 // !qFuzzyCompare(sanitizedRange.upper+1, 1))
1363 if (sanitizedRange.lower == 0.0 && sanitizedRange.upper != 0.0) {
1364 // case lower is 0
1365 if (rangeFac < sanitizedRange.upper * rangeFac)
1366 sanitizedRange.lower = rangeFac;
1367 else
1368 sanitizedRange.lower = sanitizedRange.upper * rangeFac;
1369 } // else if (!qFuzzyCompare(lower+1, 1) && qFuzzyCompare(upper+1, 1))
1370 else if (sanitizedRange.lower != 0.0 && sanitizedRange.upper == 0.0) {
1371 // case upper is 0
1372 if (-rangeFac > sanitizedRange.lower * rangeFac)
1373 sanitizedRange.upper = -rangeFac;
1374 else
1375 sanitizedRange.upper = sanitizedRange.lower * rangeFac;
1376 } else if (sanitizedRange.lower < 0 && sanitizedRange.upper > 0) {
1377 // find out whether negative or positive interval is wider to decide which
1378 // sign domain will be chosen
1379 if (-sanitizedRange.lower > sanitizedRange.upper) {
1380 // negative is wider, do same as in case upper is 0
1381 if (-rangeFac > sanitizedRange.lower * rangeFac)
1382 sanitizedRange.upper = -rangeFac;
1383 else
1384 sanitizedRange.upper = sanitizedRange.lower * rangeFac;
1385 } else {
1386 // positive is wider, do same as in case lower is 0
1387 if (rangeFac < sanitizedRange.upper * rangeFac)
1388 sanitizedRange.lower = rangeFac;
1389 else
1390 sanitizedRange.lower = sanitizedRange.upper * rangeFac;
1391 }
1392 }
1393 // due to normalization, case lower>0 && upper<0 should never occur, because
1394 // that implies upper<lower
1395 return sanitizedRange;
1396 }
1397
1398 /*!
1399 Returns a sanitized version of the range. Sanitized means for linear scales,
1400 that \a lower will always be numerically smaller (or equal) to \a upper.
1401 */
1402 QCPRange QCPRange::sanitizedForLinScale() const {
1403 QCPRange sanitizedRange(lower, upper);
1404 sanitizedRange.normalize();
1405 return sanitizedRange;
1406 }
1407
1408 /*!
1409 Returns true when \a value lies within or exactly on the borders of the range.
1410 */
1411 bool QCPRange::contains(double value) const {
1412 return value >= lower && value <= upper;
1413 }
1414
1415 /*!
1416 Checks, whether the specified range is within valid bounds, which are defined
1417 as QCPRange::maxRange and QCPRange::minRange.
1418 A valid range means:
1419 \li range bounds within -maxRange and maxRange
1420 \li range size above minRange
1421 \li range size below maxRange
1422 */
1423 bool QCPRange::validRange(double lower, double upper) {
1424 return (lower > -maxRange && upper < maxRange &&
1425 qAbs(lower - upper) > minRange && qAbs(lower - upper) < maxRange &&
1426 !(lower > 0 && qIsInf(upper / lower)) &&
1427 !(upper < 0 && qIsInf(lower / upper)));
1428 }
1429
1430 /*!
1431 \overload
1432 Checks, whether the specified range is within valid bounds, which are defined
1433 as QCPRange::maxRange and QCPRange::minRange.
1434 A valid range means:
1435 \li range bounds within -maxRange and maxRange
1436 \li range size above minRange
1437 \li range size below maxRange
1438 */
1439 bool QCPRange::validRange(const QCPRange &range) {
1440 return (range.lower > -maxRange && range.upper < maxRange &&
1441 qAbs(range.lower - range.upper) > minRange &&
1442 qAbs(range.lower - range.upper) < maxRange &&
1443 !(range.lower > 0 && qIsInf(range.upper / range.lower)) &&
1444 !(range.upper < 0 && qIsInf(range.lower / range.upper)));
1445 }
1446
1447 ////////////////////////////////////////////////////////////////////////////////////////////////////
1448 //////////////////// QCPMarginGroup
1449 ////////////////////////////////////////////////////////////////////////////////////////////////////
1450
1451 /*! \class QCPMarginGroup
1452 \brief A margin group allows synchronization of margin sides if working with
1453 multiple layout elements.
1454
1455 QCPMarginGroup allows you to tie a margin side of two or more layout elements
1456 together, such that they will all have the same size, based on the largest
1457 required margin in the group.
1458
1459 \n
1460 \image html QCPMarginGroup.png "Demonstration of QCPMarginGroup"
1461 \n
1462
1463 In certain situations it is desirable that margins at specific sides are
1464 synchronized across layout elements. For example, if one QCPAxisRect is below
1465 another one in a grid layout, it will provide a cleaner look to the user if
1466 the left and right margins of the two axis rects are of the same size. The
1467 left axis of the top axis rect will then be at the same horizontal position as
1468 the left axis of the lower axis rect, making them appear aligned. The same
1469 applies for the right axes. This is what QCPMarginGroup makes possible.
1470
1471 To add/remove a specific side of a layout element to/from a margin group, use
1472 the \ref QCPLayoutElement::setMarginGroup method. To completely break apart
1473 the margin group, either call \ref clear, or just delete the margin group.
1474
1475 \section QCPMarginGroup-example Example
1476
1477 First create a margin group:
1478 \snippet documentation/doc-code-snippets/mainwindow.cpp
1479 qcpmargingroup-creation-1 Then set this group on the layout element sides:
1480 \snippet documentation/doc-code-snippets/mainwindow.cpp
1481 qcpmargingroup-creation-2 Here, we've used the first two axis rects of the
1482 plot and synchronized their left margins with each other and their right
1483 margins with each other.
1484 */
1485
1486 /* start documentation of inline functions */
1487
1488 /*! \fn QList<QCPLayoutElement*> QCPMarginGroup::elements(QCP::MarginSide side)
1489 const
1490
1491 Returns a list of all layout elements that have their margin \a side
1492 associated with this margin group.
1493 */
1494
1495 /* end documentation of inline functions */
1496
1497 /*!
1498 Creates a new QCPMarginGroup instance in \a parentPlot.
1499 */
1500 QCPMarginGroup::QCPMarginGroup(QCustomPlot *parentPlot)
1501 : QObject(parentPlot), mParentPlot(parentPlot) {
1502 mChildren.insert(QCP::msLeft, QList<QCPLayoutElement *>());
1503 mChildren.insert(QCP::msRight, QList<QCPLayoutElement *>());
1504 mChildren.insert(QCP::msTop, QList<QCPLayoutElement *>());
1505 mChildren.insert(QCP::msBottom, QList<QCPLayoutElement *>());
1506 }
1507
1508 QCPMarginGroup::~QCPMarginGroup() { clear(); }
1509
1510 /*!
1511 Returns whether this margin group is empty. If this function returns true, no
1512 layout elements use this margin group to synchronize margin sides.
1513 */
1514 bool QCPMarginGroup::isEmpty() const {
1515 QHashIterator<QCP::MarginSide, QList<QCPLayoutElement *> > it(mChildren);
1516 while (it.hasNext()) {
1517 it.next();
1518 if (!it.value().isEmpty()) return false;
1519 }
1520 return true;
1521 }
1522
1523 /*!
1524 Clears this margin group. The synchronization of the margin sides that use
1525 this margin group is lifted and they will use their individual margin sizes
1526 again.
1527 */
1528 void QCPMarginGroup::clear() {
1529 // make all children remove themselves from this margin group:
1530 QHashIterator<QCP::MarginSide, QList<QCPLayoutElement *> > it(mChildren);
1531 while (it.hasNext()) {
1532 it.next();
1533 const QList<QCPLayoutElement *> elements = it.value();
1534 for (int i = elements.size() - 1; i >= 0; --i)
1535 elements.at(i)->setMarginGroup(
1536 it.key(), 0); // removes itself from mChildren via removeChild
1537 }
1538 }
1539
1540 /*! \internal
1541
1542 Returns the synchronized common margin for \a side. This is the margin value
1543 that will be used by the layout element on the respective side, if it is part
1544 of this margin group.
1545
1546 The common margin is calculated by requesting the automatic margin (\ref
1547 QCPLayoutElement::calculateAutoMargin) of each element associated with \a side
1548 in this margin group, and choosing the largest returned value.
1549 (QCPLayoutElement::minimumMargins is taken into account, too.)
1550 */
1551 int QCPMarginGroup::commonMargin(QCP::MarginSide side) const {
1552 // query all automatic margins of the layout elements in this margin group
1553 // side and find maximum:
1554 int result = 0;
1555 const QList<QCPLayoutElement *> elements = mChildren.value(side);
1556 for (int i = 0; i < elements.size(); ++i) {
1557 if (!elements.at(i)->autoMargins().testFlag(side)) continue;
1558 int m = qMax(elements.at(i)->calculateAutoMargin(side),
1559 QCP::getMarginValue(elements.at(i)->minimumMargins(), side));
1560 if (m > result) result = m;
1561 }
1562 return result;
1563 }
1564
1565 /*! \internal
1566
1567 Adds \a element to the internal list of child elements, for the margin \a
1568 side.
1569
1570 This function does not modify the margin group property of \a element.
1571 */
1572 void QCPMarginGroup::addChild(QCP::MarginSide side, QCPLayoutElement *element) {
1573 if (!mChildren[side].contains(element))
1574 mChildren[side].append(element);
1575 else
1576 qDebug() << Q_FUNC_INFO
1577 << "element is already child of this margin group side"
1578 << reinterpret_cast<quintptr>(element);
1579 }
1580
1581 /*! \internal
1582
1583 Removes \a element from the internal list of child elements, for the margin \a
1584 side.
1585
1586 This function does not modify the margin group property of \a element.
1587 */
1588 void QCPMarginGroup::removeChild(QCP::MarginSide side,
1589 QCPLayoutElement *element) {
1590 if (!mChildren[side].removeOne(element))
1591 qDebug() << Q_FUNC_INFO << "element is not child of this margin group side"
1592 << reinterpret_cast<quintptr>(element);
1593 }
1594
1595 ////////////////////////////////////////////////////////////////////////////////////////////////////
1596 //////////////////// QCPLayoutElement
1597 ////////////////////////////////////////////////////////////////////////////////////////////////////
1598
1599 /*! \class QCPLayoutElement
1600 \brief The abstract base class for all objects that form \ref thelayoutsystem
1601 "the layout system".
1602
1603 This is an abstract base class. As such, it can't be instantiated directly,
1604 rather use one of its subclasses.
1605
1606 A Layout element is a rectangular object which can be placed in layouts. It
1607 has an outer rect (QCPLayoutElement::outerRect) and an inner rect (\ref
1608 QCPLayoutElement::rect). The difference between outer and inner rect is called
1609 its margin. The margin can either be set to automatic or manual (\ref
1610 setAutoMargins) on a per-side basis. If a side is set to manual, that margin
1611 can be set explicitly with \ref setMargins and will stay fixed at that value.
1612 If it's set to automatic, the layout element subclass will control the value
1613 itself (via \ref calculateAutoMargin).
1614
1615 Layout elements can be placed in layouts (base class QCPLayout) like
1616 QCPLayoutGrid. The top level layout is reachable via \ref
1617 QCustomPlot::plotLayout, and is a \ref QCPLayoutGrid. Since \ref QCPLayout
1618 itself derives from \ref QCPLayoutElement, layouts can be nested.
1619
1620 Thus in QCustomPlot one can divide layout elements into two categories: The
1621 ones that are invisible by themselves, because they don't draw anything. Their
1622 only purpose is to manage the position and size of other layout elements. This
1623 category of layout elements usually use QCPLayout as base class. Then there is
1624 the category of layout elements which actually draw something. For example,
1625 QCPAxisRect, QCPLegend and QCPPlotTitle are of this category. This does not
1626 necessarily mean that the latter category can't have child layout elements.
1627 QCPLegend for instance, actually derives from QCPLayoutGrid and the individual
1628 legend items are child layout elements in the grid layout.
1629 */
1630
1631 /* start documentation of inline functions */
1632
1633 /*! \fn QCPLayout *QCPLayoutElement::layout() const
1634
1635 Returns the parent layout of this layout element.
1636 */
1637
1638 /*! \fn QRect QCPLayoutElement::rect() const
1639
1640 Returns the inner rect of this layout element. The inner rect is the outer
1641 rect (\ref setOuterRect) shrinked by the margins (\ref setMargins, \ref
1642 setAutoMargins).
1643
1644 In some cases, the area between outer and inner rect is left blank. In other
1645 cases the margin area is used to display peripheral graphics while the main
1646 content is in the inner rect. This is where automatic margin calculation
1647 becomes interesting because it allows the layout element to adapt the margins
1648 to the peripheral graphics it wants to draw. For example, \ref QCPAxisRect
1649 draws the axis labels and tick labels in the margin area, thus needs to adjust
1650 the margins (if \ref setAutoMargins is enabled) according to the space
1651 required by the labels of the axes.
1652 */
1653
1654 /*! \fn virtual void QCPLayoutElement::mousePressEvent(QMouseEvent *event)
1655
1656 This event is called, if the mouse was pressed while being inside the outer
1657 rect of this layout element.
1658 */
1659
1660 /*! \fn virtual void QCPLayoutElement::mouseMoveEvent(QMouseEvent *event)
1661
1662 This event is called, if the mouse is moved inside the outer rect of this
1663 layout element.
1664 */
1665
1666 /*! \fn virtual void QCPLayoutElement::mouseReleaseEvent(QMouseEvent *event)
1667
1668 This event is called, if the mouse was previously pressed inside the outer
1669 rect of this layout element and is now released.
1670 */
1671
1672 /*! \fn virtual void QCPLayoutElement::mouseDoubleClickEvent(QMouseEvent *event)
1673
1674 This event is called, if the mouse is double-clicked inside the outer rect of
1675 this layout element.
1676 */
1677
1678 /*! \fn virtual void QCPLayoutElement::wheelEvent(QWheelEvent *event)
1679
1680 This event is called, if the mouse wheel is scrolled while the cursor is
1681 inside the rect of this layout element.
1682 */
1683
1684 /* end documentation of inline functions */
1685
1686 /*!
1687 Creates an instance of QCPLayoutElement and sets default values.
1688 */
1689 QCPLayoutElement::QCPLayoutElement(QCustomPlot *parentPlot)
1690 : QCPLayerable(
1691 parentPlot), // parenthood is changed as soon as layout element gets
1692 // inserted into a layout (except for top level layout)
1693 mParentLayout(0),
1694 mMinimumSize(),
1695 mMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX),
1696 mRect(0, 0, 0, 0),
1697 mOuterRect(0, 0, 0, 0),
1698 mMargins(0, 0, 0, 0),
1699 mMinimumMargins(0, 0, 0, 0),
1700 mAutoMargins(QCP::msAll) {}
1701
1702 QCPLayoutElement::~QCPLayoutElement() {
1703 setMarginGroup(QCP::msAll,
1704 0); // unregister at margin groups, if there are any
1705 // unregister at layout:
1706 if (qobject_cast<QCPLayout *>(
1707 mParentLayout)) // the qobject_cast is just a safeguard in case the
1708 // layout forgets to call clear() in its dtor and
1709 // this dtor is called by QObject dtor
1710 mParentLayout->take(this);
1711 }
1712
1713 /*!
1714 Sets the outer rect of this layout element. If the layout element is inside a
1715 layout, the layout sets the position and size of this layout element using
1716 this function.
1717
1718 Calling this function externally has no effect, since the layout will
1719 overwrite any changes to the outer rect upon the next replot.
1720
1721 The layout element will adapt its inner \ref rect by applying the margins
1722 inward to the outer rect.
1723
1724 \see rect
1725 */
1726 void QCPLayoutElement::setOuterRect(const QRect &rect) {
1727 if (mOuterRect != rect) {
1728 mOuterRect = rect;
1729 mRect = mOuterRect.adjusted(mMargins.left(), mMargins.top(),
1730 -mMargins.right(), -mMargins.bottom());
1731 }
1732 }
1733
1734 /*!
1735 Sets the margins of this layout element. If \ref setAutoMargins is disabled
1736 for some or all sides, this function is used to manually set the margin on
1737 those sides. Sides that are still set to be handled automatically are ignored
1738 and may have any value in \a margins.
1739
1740 The margin is the distance between the outer rect (controlled by the parent
1741 layout via \ref setOuterRect) and the inner \ref rect (which usually contains
1742 the main content of this layout element).
1743
1744 \see setAutoMargins
1745 */
1746 void QCPLayoutElement::setMargins(const QMargins &margins) {
1747 if (mMargins != margins) {
1748 mMargins = margins;
1749 mRect = mOuterRect.adjusted(mMargins.left(), mMargins.top(),
1750 -mMargins.right(), -mMargins.bottom());
1751 }
1752 }
1753
1754 /*!
1755 If \ref setAutoMargins is enabled on some or all margins, this function is
1756 used to provide minimum values for those margins.
1757
1758 The minimum values are not enforced on margin sides that were set to be under
1759 manual control via \ref setAutoMargins.
1760
1761 \see setAutoMargins
1762 */
1763 void QCPLayoutElement::setMinimumMargins(const QMargins &margins) {
1764 if (mMinimumMargins != margins) {
1765 mMinimumMargins = margins;
1766 }
1767 }
1768
1769 /*!
1770 Sets on which sides the margin shall be calculated automatically. If a side is
1771 calculated automatically, a minimum margin value may be provided with \ref
1772 setMinimumMargins. If a side is set to be controlled manually, the value may
1773 be specified with \ref setMargins.
1774
1775 Margin sides that are under automatic control may participate in a \ref
1776 QCPMarginGroup (see \ref setMarginGroup), to synchronize (align) it with other
1777 layout elements in the plot.
1778
1779 \see setMinimumMargins, setMargins
1780 */
1781 void QCPLayoutElement::setAutoMargins(QCP::MarginSides sides) {
1782 mAutoMargins = sides;
1783 }
1784
1785 /*!
1786 Sets the minimum size for the inner \ref rect of this layout element. A parent
1787 layout tries to respect the \a size here by changing row/column sizes in the
1788 layout accordingly.
1789
1790 If the parent layout size is not sufficient to satisfy all minimum size
1791 constraints of its child layout elements, the layout may set a size that is
1792 actually smaller than \a size. QCustomPlot propagates the layout's size
1793 constraints to the outside by setting its own minimum QWidget size
1794 accordingly, so violations of \a size should be exceptions.
1795 */
1796 void QCPLayoutElement::setMinimumSize(const QSize &size) {
1797 if (mMinimumSize != size) {
1798 mMinimumSize = size;
1799 if (mParentLayout) mParentLayout->sizeConstraintsChanged();
1800 }
1801 }
1802
1803 /*! \overload
1804
1805 Sets the minimum size for the inner \ref rect of this layout element.
1806 */
1807 void QCPLayoutElement::setMinimumSize(int width, int height) {
1808 setMinimumSize(QSize(width, height));
1809 }
1810
1811 /*!
1812 Sets the maximum size for the inner \ref rect of this layout element. A parent
1813 layout tries to respect the \a size here by changing row/column sizes in the
1814 layout accordingly.
1815 */
1816 void QCPLayoutElement::setMaximumSize(const QSize &size) {
1817 if (mMaximumSize != size) {
1818 mMaximumSize = size;
1819 if (mParentLayout) mParentLayout->sizeConstraintsChanged();
1820 }
1821 }
1822
1823 /*! \overload
1824
1825 Sets the maximum size for the inner \ref rect of this layout element.
1826 */
1827 void QCPLayoutElement::setMaximumSize(int width, int height) {
1828 setMaximumSize(QSize(width, height));
1829 }
1830
1831 /*!
1832 Sets the margin \a group of the specified margin \a sides.
1833
1834 Margin groups allow synchronizing specified margins across layout elements,
1835 see the documentation of \ref QCPMarginGroup.
1836
1837 To unset the margin group of \a sides, set \a group to 0.
1838
1839 Note that margin groups only work for margin sides that are set to automatic
1840 (\ref setAutoMargins).
1841 */
1842 void QCPLayoutElement::setMarginGroup(QCP::MarginSides sides,
1843 QCPMarginGroup *group) {
1844 QVector<QCP::MarginSide> sideVector;
1845 if (sides.testFlag(QCP::msLeft)) sideVector.append(QCP::msLeft);
1846 if (sides.testFlag(QCP::msRight)) sideVector.append(QCP::msRight);
1847 if (sides.testFlag(QCP::msTop)) sideVector.append(QCP::msTop);
1848 if (sides.testFlag(QCP::msBottom)) sideVector.append(QCP::msBottom);
1849
1850 for (int i = 0; i < sideVector.size(); ++i) {
1851 QCP::MarginSide side = sideVector.at(i);
1852 if (marginGroup(side) != group) {
1853 QCPMarginGroup *oldGroup = marginGroup(side);
1854 if (oldGroup) // unregister at old group
1855 oldGroup->removeChild(side, this);
1856
1857 if (!group) // if setting to 0, remove hash entry. Else set hash entry to
1858 // new group and register there
1859 {
1860 mMarginGroups.remove(side);
1861 } else // setting to a new group
1862 {
1863 mMarginGroups[side] = group;
1864 group->addChild(side, this);
1865 }
1866 }
1867 }
1868 }
1869
1870 /*!
1871 Updates the layout element and sub-elements. This function is automatically
1872 called before every replot by the parent layout element. It is called multiple
1873 times, once for every \ref UpdatePhase. The phases are run through in the
1874 order of the enum values. For details about what happens at the different
1875 phases, see the documentation of \ref UpdatePhase.
1876
1877 Layout elements that have child elements should call the \ref update method of
1878 their child elements, and pass the current \a phase unchanged.
1879
1880 The default implementation executes the automatic margin mechanism in the \ref
1881 upMargins phase. Subclasses should make sure to call the base class
1882 implementation.
1883 */
1884 void QCPLayoutElement::update(UpdatePhase phase) {
1885 if (phase == upMargins) {
1886 if (mAutoMargins != QCP::msNone) {
1887 // set the margins of this layout element according to automatic margin
1888 // calculation, either directly or via a margin group:
1889 QMargins newMargins = mMargins;
1890 QList<QCP::MarginSide> allMarginSides = QList<QCP::MarginSide>()
1891 << QCP::msLeft << QCP::msRight
1892 << QCP::msTop << QCP::msBottom;
1893 foreach (QCP::MarginSide side, allMarginSides) {
1894 if (mAutoMargins.testFlag(
1895 side)) // this side's margin shall be calculated automatically
1896 {
1897 if (mMarginGroups.contains(side))
1898 QCP::setMarginValue(
1899 newMargins, side,
1900 mMarginGroups[side]->commonMargin(
1901 side)); // this side is part of a margin group, so get the
1902 // margin value from that group
1903 else
1904 QCP::setMarginValue(
1905 newMargins, side,
1906 calculateAutoMargin(
1907 side)); // this side is not part of a group, so calculate
1908 // the value directly
1909 // apply minimum margin restrictions:
1910 if (QCP::getMarginValue(newMargins, side) <
1911 QCP::getMarginValue(mMinimumMargins, side))
1912 QCP::setMarginValue(newMargins, side,
1913 QCP::getMarginValue(mMinimumMargins, side));
1914 }
1915 }
1916 setMargins(newMargins);
1917 }
1918 }
1919 }
1920
1921 /*!
1922 Returns the minimum size this layout element (the inner \ref rect) may be
1923 compressed to.
1924
1925 if a minimum size (\ref setMinimumSize) was not set manually, parent layouts
1926 consult this function to determine the minimum allowed size of this layout
1927 element. (A manual minimum size is considered set if it is non-zero.)
1928 */
1929 QSize QCPLayoutElement::minimumSizeHint() const { return mMinimumSize; }
1930
1931 /*!
1932 Returns the maximum size this layout element (the inner \ref rect) may be
1933 expanded to.
1934
1935 if a maximum size (\ref setMaximumSize) was not set manually, parent layouts
1936 consult this function to determine the maximum allowed size of this layout
1937 element. (A manual maximum size is considered set if it is smaller than Qt's
1938 QWIDGETSIZE_MAX.)
1939 */
1940 QSize QCPLayoutElement::maximumSizeHint() const { return mMaximumSize; }
1941
1942 /*!
1943 Returns a list of all child elements in this layout element. If \a recursive
1944 is true, all sub-child elements are included in the list, too.
1945
1946 \warning There may be entries with value 0 in the returned list. (For example,
1947 QCPLayoutGrid may have empty cells which yield 0 at the respective index.)
1948 */
1949 QList<QCPLayoutElement *> QCPLayoutElement::elements(bool recursive) const {
1950 Q_UNUSED(recursive)
1951 return QList<QCPLayoutElement *>();
1952 }
1953
1954 /*!
1955 Layout elements are sensitive to events inside their outer rect. If \a pos is
1956 within the outer rect, this method returns a value corresponding to 0.99 times
1957 the parent plot's selection tolerance. However, layout elements are not
1958 selectable by default. So if \a onlySelectable is true, -1.0 is returned.
1959
1960 See \ref QCPLayerable::selectTest for a general explanation of this virtual
1961 method.
1962
1963 QCPLayoutElement subclasses may reimplement this method to provide more
1964 specific selection test behaviour.
1965 */
1966 double QCPLayoutElement::selectTest(const QPointF &pos, bool onlySelectable,
1967 QVariant *details) const {
1968 Q_UNUSED(details)
1969
1970 if (onlySelectable) return -1;
1971
1972 if (QRectF(mOuterRect).contains(pos)) {
1973 if (mParentPlot)
1974 return mParentPlot->selectionTolerance() * 0.99;
1975 else {
1976 qDebug() << Q_FUNC_INFO << "parent plot not defined";
1977 return -1;
1978 }
1979 } else
1980 return -1;
1981 }
1982
1983 /*! \internal
1984
1985 propagates the parent plot initialization to all child elements, by calling
1986 \ref QCPLayerable::initializeParentPlot on them.
1987 */
1988 void QCPLayoutElement::parentPlotInitialized(QCustomPlot *parentPlot) {
1989 foreach (QCPLayoutElement *el, elements(false)) {
1990 if (!el->parentPlot()) el->initializeParentPlot(parentPlot);
1991 }
1992 }
1993
1994 /*! \internal
1995
1996 Returns the margin size for this \a side. It is used if automatic margins is
1997 enabled for this \a side (see \ref setAutoMargins). If a minimum margin was
1998 set with \ref setMinimumMargins, the returned value will not be smaller than
1999 the specified minimum margin.
2000
2001 The default implementation just returns the respective manual margin (\ref
2002 setMargins) or the minimum margin, whichever is larger.
2003 */
2004 int QCPLayoutElement::calculateAutoMargin(QCP::MarginSide side) {
2005 return qMax(QCP::getMarginValue(mMargins, side),
2006 QCP::getMarginValue(mMinimumMargins, side));
2007 }
2008
2009 ////////////////////////////////////////////////////////////////////////////////////////////////////
2010 //////////////////// QCPLayout
2011 ////////////////////////////////////////////////////////////////////////////////////////////////////
2012
2013 /*! \class QCPLayout
2014 \brief The abstract base class for layouts
2015
2016 This is an abstract base class for layout elements whose main purpose is to
2017 define the position and size of other child layout elements. In most cases,
2018 layouts don't draw anything themselves (but there are exceptions to this, e.g.
2019 QCPLegend).
2020
2021 QCPLayout derives from QCPLayoutElement, and thus can itself be nested in
2022 other layouts.
2023
2024 QCPLayout introduces a common interface for accessing and manipulating the
2025 child elements. Those functions are most notably \ref elementCount, \ref
2026 elementAt, \ref takeAt, \ref take, \ref simplify, \ref removeAt, \ref remove
2027 and \ref clear. Individual subclasses may add more functions to this interface
2028 which are more specialized to the form of the layout. For example, \ref
2029 QCPLayoutGrid adds functions that take row and column indices to access cells
2030 of the layout grid more conveniently.
2031
2032 Since this is an abstract base class, you can't instantiate it directly.
2033 Rather use one of its subclasses like QCPLayoutGrid or QCPLayoutInset.
2034
2035 For a general introduction to the layout system, see the dedicated
2036 documentation page \ref thelayoutsystem "The Layout System".
2037 */
2038
2039 /* start documentation of pure virtual functions */
2040
2041 /*! \fn virtual int QCPLayout::elementCount() const = 0
2042
2043 Returns the number of elements/cells in the layout.
2044
2045 \see elements, elementAt
2046 */
2047
2048 /*! \fn virtual QCPLayoutElement* QCPLayout::elementAt(int index) const = 0
2049
2050 Returns the element in the cell with the given \a index. If \a index is
2051 invalid, returns 0.
2052
2053 Note that even if \a index is valid, the respective cell may be empty in some
2054 layouts (e.g. QCPLayoutGrid), so this function may return 0 in those cases.
2055 You may use this function to check whether a cell is empty or not.
2056
2057 \see elements, elementCount, takeAt
2058 */
2059
2060 /*! \fn virtual QCPLayoutElement* QCPLayout::takeAt(int index) = 0
2061
2062 Removes the element with the given \a index from the layout and returns it.
2063
2064 If the \a index is invalid or the cell with that index is empty, returns 0.
2065
2066 Note that some layouts don't remove the respective cell right away but leave
2067 an empty cell after successful removal of the layout element. To collapse
2068 empty cells, use \ref simplify.
2069
2070 \see elementAt, take
2071 */
2072
2073 /*! \fn virtual bool QCPLayout::take(QCPLayoutElement* element) = 0
2074
2075 Removes the specified \a element from the layout and returns true on success.
2076
2077 If the \a element isn't in this layout, returns false.
2078
2079 Note that some layouts don't remove the respective cell right away but leave
2080 an empty cell after successful removal of the layout element. To collapse
2081 empty cells, use \ref simplify.
2082
2083 \see takeAt
2084 */
2085
2086 /* end documentation of pure virtual functions */
2087
2088 /*!
2089 Creates an instance of QCPLayout and sets default values. Note that since
2090 QCPLayout is an abstract base class, it can't be instantiated directly.
2091 */
2092 QCPLayout::QCPLayout() {}
2093
2094 /*!
2095 First calls the QCPLayoutElement::update base class implementation to update
2096 the margins on this layout.
2097
2098 Then calls \ref updateLayout which subclasses reimplement to reposition and
2099 resize their cells.
2100
2101 Finally, \ref update is called on all child elements.
2102 */
2103 void QCPLayout::update(UpdatePhase phase) {
2104 QCPLayoutElement::update(phase);
2105
2106 // set child element rects according to layout:
2107 if (phase == upLayout) updateLayout();
2108
2109 // propagate update call to child elements:
2110 const int elCount = elementCount();
2111 for (int i = 0; i < elCount; ++i) {
2112 if (QCPLayoutElement *el = elementAt(i)) el->update(phase);
2113 }
2114 }
2115
2116 /* inherits documentation from base class */
2117 QList<QCPLayoutElement *> QCPLayout::elements(bool recursive) const {
2118 const int c = elementCount();
2119 QList<QCPLayoutElement *> result;
2120 #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
2121 result.reserve(c);
2122 #endif
2123 for (int i = 0; i < c; ++i) result.append(elementAt(i));
2124 if (recursive) {
2125 for (int i = 0; i < c; ++i) {
2126 if (result.at(i)) result << result.at(i)->elements(recursive);
2127 }
2128 }
2129 return result;
2130 }
2131
2132 /*!
2133 Simplifies the layout by collapsing empty cells. The exact behavior depends on
2134 subclasses, the default implementation does nothing.
2135
2136 Not all layouts need simplification. For example, QCPLayoutInset doesn't use
2137 explicit simplification while QCPLayoutGrid does.
2138 */
2139 void QCPLayout::simplify() {}
2140
2141 /*!
2142 Removes and deletes the element at the provided \a index. Returns true on
2143 success. If \a index is invalid or points to an empty cell, returns false.
2144
2145 This function internally uses \ref takeAt to remove the element from the
2146 layout and then deletes the returned element. Note that some layouts don't
2147 remove the respective cell right away but leave an empty cell after successful
2148 removal of the layout element. To collapse empty cells, use \ref simplify.
2149
2150 \see remove, takeAt
2151 */
2152 bool QCPLayout::removeAt(int index) {
2153 if (QCPLayoutElement *el = takeAt(index)) {
2154 delete el;
2155 return true;
2156 } else
2157 return false;
2158 }
2159
2160 /*!
2161 Removes and deletes the provided \a element. Returns true on success. If \a
2162 element is not in the layout, returns false.
2163
2164 This function internally uses \ref takeAt to remove the element from the
2165 layout and then deletes the element. Note that some layouts don't remove the
2166 respective cell right away but leave an empty cell after successful removal of
2167 the layout element. To collapse empty cells, use \ref simplify.
2168
2169 \see removeAt, take
2170 */
2171 bool QCPLayout::remove(QCPLayoutElement *element) {
2172 if (take(element)) {
2173 delete element;
2174 return true;
2175 } else
2176 return false;
2177 }
2178
2179 /*!
2180 Removes and deletes all layout elements in this layout. Finally calls \ref
2181 simplify to make sure all empty cells are collapsed.
2182
2183 \see remove, removeAt
2184 */
2185 void QCPLayout::clear() {
2186 for (int i = elementCount() - 1; i >= 0; --i) {
2187 if (elementAt(i)) removeAt(i);
2188 }
2189 simplify();
2190 }
2191
2192 /*!
2193 Subclasses call this method to report changed (minimum/maximum) size
2194 constraints.
2195
2196 If the parent of this layout is again a QCPLayout, forwards the call to the
2197 parent's \ref sizeConstraintsChanged. If the parent is a QWidget (i.e. is the
2198 \ref QCustomPlot::plotLayout of QCustomPlot), calls QWidget::updateGeometry,
2199 so if the QCustomPlot widget is inside a Qt QLayout, it may update itself and
2200 resize cells accordingly.
2201 */
2202 void QCPLayout::sizeConstraintsChanged() const {
2203 if (QWidget *w = qobject_cast<QWidget *>(parent()))
2204 w->updateGeometry();
2205 else if (QCPLayout *l = qobject_cast<QCPLayout *>(parent()))
2206 l->sizeConstraintsChanged();
2207 }
2208
2209 /*! \internal
2210
2211 Subclasses reimplement this method to update the position and sizes of the
2212 child elements/cells via calling their \ref QCPLayoutElement::setOuterRect.
2213 The default implementation does nothing.
2214
2215 The geometry used as a reference is the inner \ref rect of this layout. Child
2216 elements should stay within that rect.
2217
2218 \ref getSectionSizes may help with the reimplementation of this function.
2219
2220 \see update
2221 */
2222 void QCPLayout::updateLayout() {}
2223
2224 /*! \internal
2225
2226 Associates \a el with this layout. This is done by setting the \ref
2227 QCPLayoutElement::layout, the \ref QCPLayerable::parentLayerable and the
2228 QObject parent to this layout.
2229
2230 Further, if \a el didn't previously have a parent plot, calls \ref
2231 QCPLayerable::initializeParentPlot on \a el to set the paret plot.
2232
2233 This method is used by subclass specific methods that add elements to the
2234 layout. Note that this method only changes properties in \a el. The removal
2235 from the old layout and the insertion into the new layout must be done
2236 additionally.
2237 */
2238 void QCPLayout::adoptElement(QCPLayoutElement *el) {
2239 if (el) {
2240 el->mParentLayout = this;
2241 el->setParentLayerable(this);
2242 el->setParent(this);
2243 if (!el->parentPlot()) el->initializeParentPlot(mParentPlot);
2244 } else
2245 qDebug() << Q_FUNC_INFO << "Null element passed";
2246 }
2247
2248 /*! \internal
2249
2250 Disassociates \a el from this layout. This is done by setting the \ref
2251 QCPLayoutElement::layout and the \ref QCPLayerable::parentLayerable to zero.
2252 The QObject parent is set to the parent QCustomPlot.
2253
2254 This method is used by subclass specific methods that remove elements from the
2255 layout (e.g. \ref take or \ref takeAt). Note that this method only changes
2256 properties in \a el. The removal from the old layout must be done
2257 additionally.
2258 */
2259 void QCPLayout::releaseElement(QCPLayoutElement *el) {
2260 if (el) {
2261 el->mParentLayout = 0;
2262 el->setParentLayerable(0);
2263 el->setParent(mParentPlot);
2264 // Note: Don't initializeParentPlot(0) here, because layout element will
2265 // stay in same parent plot
2266 } else
2267 qDebug() << Q_FUNC_INFO << "Null element passed";
2268 }
2269
2270 /*! \internal
2271
2272 This is a helper function for the implementation of \ref updateLayout in
2273 subclasses.
2274
2275 It calculates the sizes of one-dimensional sections with provided constraints
2276 on maximum section sizes, minimum section sizes, relative stretch factors and
2277 the final total size of all sections.
2278
2279 The QVector entries refer to the sections. Thus all QVectors must have the
2280 same size.
2281
2282 \a maxSizes gives the maximum allowed size of each section. If there shall be
2283 no maximum size imposed, set all vector values to Qt's QWIDGETSIZE_MAX.
2284
2285 \a minSizes gives the minimum allowed size of each section. If there shall be
2286 no minimum size imposed, set all vector values to zero. If the \a minSizes
2287 entries add up to a value greater than \a totalSize, sections will be scaled
2288 smaller than the proposed minimum sizes. (In other words, not exceeding the
2289 allowed total size is taken to be more important than not going below minimum
2290 section sizes.)
2291
2292 \a stretchFactors give the relative proportions of the sections to each other.
2293 If all sections shall be scaled equally, set all values equal. If the first
2294 section shall be double the size of each individual other section, set the
2295 first number of \a stretchFactors to double the value of the other individual
2296 values (e.g. {2, 1, 1, 1}).
2297
2298 \a totalSize is the value that the final section sizes will add up to. Due to
2299 rounding, the actual sum may differ slightly. If you want the section sizes to
2300 sum up to exactly that value, you could distribute the remaining difference on
2301 the sections.
2302
2303 The return value is a QVector containing the section sizes.
2304 */
2305 QVector<int> QCPLayout::getSectionSizes(QVector<int> maxSizes,
2306 QVector<int> minSizes,
2307 QVector<double> stretchFactors,
2308 int totalSize) const {
2309 if (maxSizes.size() != minSizes.size() ||
2310 minSizes.size() != stretchFactors.size()) {
2311 qDebug() << Q_FUNC_INFO << "Passed vector sizes aren't equal:" << maxSizes
2312 << minSizes << stretchFactors;
2313 return QVector<int>();
2314 }
2315 if (stretchFactors.isEmpty()) return QVector<int>();
2316 int sectionCount = stretchFactors.size();
2317 QVector<double> sectionSizes(sectionCount);
2318 // if provided total size is forced smaller than total minimum size, ignore
2319 // minimum sizes (squeeze sections):
2320 int minSizeSum = 0;
2321 for (int i = 0; i < sectionCount; ++i) minSizeSum += minSizes.at(i);
2322 if (totalSize < minSizeSum) {
2323 // new stretch factors are minimum sizes and minimum sizes are set to zero:
2324 for (int i = 0; i < sectionCount; ++i) {
2325 stretchFactors[i] = minSizes.at(i);
2326 minSizes[i] = 0;
2327 }
2328 }
2329
2330 QList<int> minimumLockedSections;
2331 QList<int> unfinishedSections;
2332 for (int i = 0; i < sectionCount; ++i) unfinishedSections.append(i);
2333 double freeSize = totalSize;
2334
2335 int outerIterations = 0;
2336 while (!unfinishedSections.isEmpty() &&
2337 outerIterations <
2338 sectionCount * 2) // the iteration check ist just a failsafe in
2339 // case something really strange happens
2340 {
2341 ++outerIterations;
2342 int innerIterations = 0;
2343 while (!unfinishedSections.isEmpty() &&
2344 innerIterations <
2345 sectionCount * 2) // the iteration check ist just a failsafe in
2346 // case something really strange happens
2347 {
2348 ++innerIterations;
2349 // find section that hits its maximum next:
2350 int nextId = -1;
2351 double nextMax = 1e12;
2352 for (int i = 0; i < unfinishedSections.size(); ++i) {
2353 int secId = unfinishedSections.at(i);
2354 double hitsMaxAt = (maxSizes.at(secId) - sectionSizes.at(secId)) /
2355 stretchFactors.at(secId);
2356 if (hitsMaxAt < nextMax) {
2357 nextMax = hitsMaxAt;
2358 nextId = secId;
2359 }
2360 }
2361 // check if that maximum is actually within the bounds of the total size
2362 // (i.e. can we stretch all remaining sections so far that the found
2363 // section actually hits its maximum, without exceeding the total size
2364 // when we add up all sections)
2365 double stretchFactorSum = 0;
2366 for (int i = 0; i < unfinishedSections.size(); ++i)
2367 stretchFactorSum += stretchFactors.at(unfinishedSections.at(i));
2368 double nextMaxLimit = freeSize / stretchFactorSum;
2369 if (nextMax <
2370 nextMaxLimit) // next maximum is actually hit, move forward to that
2371 // point and fix the size of that section
2372 {
2373 for (int i = 0; i < unfinishedSections.size(); ++i) {
2374 sectionSizes[unfinishedSections.at(i)] +=
2375 nextMax * stretchFactors.at(unfinishedSections.at(
2376 i)); // increment all sections
2377 freeSize -= nextMax * stretchFactors.at(unfinishedSections.at(i));
2378 }
2379 unfinishedSections.removeOne(
2380 nextId); // exclude the section that is now at maximum from further
2381 // changes
2382 } else // next maximum isn't hit, just distribute rest of free space on
2383 // remaining sections
2384 {
2385 for (int i = 0; i < unfinishedSections.size(); ++i)
2386 sectionSizes[unfinishedSections.at(i)] +=
2387 nextMaxLimit * stretchFactors.at(unfinishedSections.at(
2388 i)); // increment all sections
2389 unfinishedSections.clear();
2390 }
2391 }
2392 if (innerIterations == sectionCount * 2)
2393 qDebug() << Q_FUNC_INFO
2394 << "Exceeded maximum expected inner iteration count, layouting "
2395 "aborted. Input was:"
2396 << maxSizes << minSizes << stretchFactors << totalSize;
2397
2398 // now check whether the resulting section sizes violate minimum
2399 // restrictions:
2400 bool foundMinimumViolation = false;
2401 for (int i = 0; i < sectionSizes.size(); ++i) {
2402 if (minimumLockedSections.contains(i)) continue;
2403 if (sectionSizes.at(i) < minSizes.at(i)) // section violates minimum
2404 {
2405 sectionSizes[i] = minSizes.at(i); // set it to minimum
2406 foundMinimumViolation =
2407 true; // make sure we repeat the whole optimization process
2408 minimumLockedSections.append(i);
2409 }
2410 }
2411 if (foundMinimumViolation) {
2412 freeSize = totalSize;
2413 for (int i = 0; i < sectionCount; ++i) {
2414 if (!minimumLockedSections.contains(
2415 i)) // only put sections that haven't hit their minimum back
2416 // into the pool
2417 unfinishedSections.append(i);
2418 else
2419 freeSize -=
2420 sectionSizes.at(i); // remove size of minimum locked sections
2421 // from available space in next round
2422 }
2423 // reset all section sizes to zero that are in unfinished sections (all
2424 // others have been set to their minimum):
2425 for (int i = 0; i < unfinishedSections.size(); ++i)
2426 sectionSizes[unfinishedSections.at(i)] = 0;
2427 }
2428 }
2429 if (outerIterations == sectionCount * 2)
2430 qDebug() << Q_FUNC_INFO
2431 << "Exceeded maximum expected outer iteration count, layouting "
2432 "aborted. Input was:"
2433 << maxSizes << minSizes << stretchFactors << totalSize;
2434
2435 QVector<int> result(sectionCount);
2436 for (int i = 0; i < sectionCount; ++i) result[i] = qRound(sectionSizes.at(i));
2437 return result;
2438 }
2439
2440 ////////////////////////////////////////////////////////////////////////////////////////////////////
2441 //////////////////// QCPLayoutGrid
2442 ////////////////////////////////////////////////////////////////////////////////////////////////////
2443
2444 /*! \class QCPLayoutGrid
2445 \brief A layout that arranges child elements in a grid
2446
2447 Elements are laid out in a grid with configurable stretch factors (\ref
2448 setColumnStretchFactor, \ref setRowStretchFactor) and spacing (\ref
2449 setColumnSpacing, \ref setRowSpacing).
2450
2451 Elements can be added to cells via \ref addElement. The grid is expanded if
2452 the specified row or column doesn't exist yet. Whether a cell contains a valid
2453 layout element can be checked with \ref hasElement, that element can be
2454 retrieved with \ref element. If rows and columns that only have empty cells
2455 shall be removed, call \ref simplify. Removal of elements is either done by
2456 just adding the element to a different layout or by using the QCPLayout
2457 interface \ref take or \ref remove.
2458
2459 Row and column insertion can be performed with \ref insertRow and \ref
2460 insertColumn.
2461 */
2462
2463 /*!
2464 Creates an instance of QCPLayoutGrid and sets default values.
2465 */
2466 QCPLayoutGrid::QCPLayoutGrid() : mColumnSpacing(5), mRowSpacing(5) {}
2467
2468 QCPLayoutGrid::~QCPLayoutGrid() {
2469 // clear all child layout elements. This is important because only the
2470 // specific layouts know how to handle removing elements (clear calls virtual
2471 // removeAt method to do that).
2472 clear();
2473 }
2474
2475 /*!
2476 Returns the element in the cell in \a row and \a column.
2477
2478 Returns 0 if either the row/column is invalid or if the cell is empty. In
2479 those cases, a qDebug message is printed. To check whether a cell exists and
2480 isn't empty, use \ref hasElement.
2481
2482 \see addElement, hasElement
2483 */
2484 QCPLayoutElement *QCPLayoutGrid::element(int row, int column) const {
2485 if (row >= 0 && row < mElements.size()) {
2486 if (column >= 0 && column < mElements.first().size()) {
2487 if (QCPLayoutElement *result = mElements.at(row).at(column))
2488 return result;
2489 else
2490 qDebug() << Q_FUNC_INFO << "Requested cell is empty. Row:" << row
2491 << "Column:" << column;
2492 } else
2493 qDebug() << Q_FUNC_INFO << "Invalid column. Row:" << row
2494 << "Column:" << column;
2495 } else
2496 qDebug() << Q_FUNC_INFO << "Invalid row. Row:" << row
2497 << "Column:" << column;
2498 return 0;
2499 }
2500
2501 /*!
2502 Returns the number of rows in the layout.
2503
2504 \see columnCount
2505 */
2506 int QCPLayoutGrid::rowCount() const { return mElements.size(); }
2507
2508 /*!
2509 Returns the number of columns in the layout.
2510
2511 \see rowCount
2512 */
2513 int QCPLayoutGrid::columnCount() const {
2514 if (mElements.size() > 0)
2515 return mElements.first().size();
2516 else
2517 return 0;
2518 }
2519
2520 /*!
2521 Adds the \a element to cell with \a row and \a column. If \a element is
2522 already in a layout, it is first removed from there. If \a row or \a column
2523 don't exist yet, the layout is expanded accordingly.
2524
2525 Returns true if the element was added successfully, i.e. if the cell at \a row
2526 and \a column didn't already have an element.
2527
2528 \see element, hasElement, take, remove
2529 */
2530 bool QCPLayoutGrid::addElement(int row, int column, QCPLayoutElement *element) {
2531 if (element) {
2532 if (!hasElement(row, column)) {
2533 if (element->layout()) // remove from old layout first
2534 element->layout()->take(element);
2535 expandTo(row + 1, column + 1);
2536 mElements[row][column] = element;
2537 adoptElement(element);
2538 return true;
2539 } else
2540 qDebug() << Q_FUNC_INFO
2541 << "There is already an element in the specified row/column:"
2542 << row << column;
2543 } else
2544 qDebug() << Q_FUNC_INFO << "Can't add null element to row/column:" << row
2545 << column;
2546 return false;
2547 }
2548
2549 /*!
2550 Returns whether the cell at \a row and \a column exists and contains a valid
2551 element, i.e. isn't empty.
2552
2553 \see element
2554 */
2555 bool QCPLayoutGrid::hasElement(int row, int column) {
2556 if (row >= 0 && row < rowCount() && column >= 0 && column < columnCount())
2557 return mElements.at(row).at(column);
2558 else
2559 return false;
2560 }
2561
2562 /*!
2563 Sets the stretch \a factor of \a column.
2564
2565 Stretch factors control the relative sizes of rows and columns. Cells will not
2566 be resized beyond their minimum and maximum widths/heights (\ref
2567 QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize),
2568 regardless of the stretch factor.
2569
2570 The default stretch factor of newly created rows/columns is 1.
2571
2572 \see setColumnStretchFactors, setRowStretchFactor
2573 */
2574 void QCPLayoutGrid::setColumnStretchFactor(int column, double factor) {
2575 if (column >= 0 && column < columnCount()) {
2576 if (factor > 0)
2577 mColumnStretchFactors[column] = factor;
2578 else
2579 qDebug() << Q_FUNC_INFO
2580 << "Invalid stretch factor, must be positive:" << factor;
2581 } else
2582 qDebug() << Q_FUNC_INFO << "Invalid column:" << column;
2583 }
2584
2585 /*!
2586 Sets the stretch \a factors of all columns. \a factors must have the size \ref
2587 columnCount.
2588
2589 Stretch factors control the relative sizes of rows and columns. Cells will not
2590 be resized beyond their minimum and maximum widths/heights (\ref
2591 QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize),
2592 regardless of the stretch factor.
2593
2594 The default stretch factor of newly created rows/columns is 1.
2595
2596 \see setColumnStretchFactor, setRowStretchFactors
2597 */
2598 void QCPLayoutGrid::setColumnStretchFactors(const QList<double> &factors) {
2599 if (factors.size() == mColumnStretchFactors.size()) {
2600 mColumnStretchFactors = factors;
2601 for (int i = 0; i < mColumnStretchFactors.size(); ++i) {
2602 if (mColumnStretchFactors.at(i) <= 0) {
2603 qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:"
2604 << mColumnStretchFactors.at(i);
2605 mColumnStretchFactors[i] = 1;
2606 }
2607 }
2608 } else
2609 qDebug() << Q_FUNC_INFO
2610 << "Column count not equal to passed stretch factor count:"
2611 << factors;
2612 }
2613
2614 /*!
2615 Sets the stretch \a factor of \a row.
2616
2617 Stretch factors control the relative sizes of rows and columns. Cells will not
2618 be resized beyond their minimum and maximum widths/heights (\ref
2619 QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize),
2620 regardless of the stretch factor.
2621
2622 The default stretch factor of newly created rows/columns is 1.
2623
2624 \see setColumnStretchFactors, setRowStretchFactor
2625 */
2626 void QCPLayoutGrid::setRowStretchFactor(int row, double factor) {
2627 if (row >= 0 && row < rowCount()) {
2628 if (factor > 0)
2629 mRowStretchFactors[row] = factor;
2630 else
2631 qDebug() << Q_FUNC_INFO
2632 << "Invalid stretch factor, must be positive:" << factor;
2633 } else
2634 qDebug() << Q_FUNC_INFO << "Invalid row:" << row;
2635 }
2636
2637 /*!
2638 Sets the stretch \a factors of all rows. \a factors must have the size \ref
2639 rowCount.
2640
2641 Stretch factors control the relative sizes of rows and columns. Cells will not
2642 be resized beyond their minimum and maximum widths/heights (\ref
2643 QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize),
2644 regardless of the stretch factor.
2645
2646 The default stretch factor of newly created rows/columns is 1.
2647
2648 \see setRowStretchFactor, setColumnStretchFactors
2649 */
2650 void QCPLayoutGrid::setRowStretchFactors(const QList<double> &factors) {
2651 if (factors.size() == mRowStretchFactors.size()) {
2652 mRowStretchFactors = factors;
2653 for (int i = 0; i < mRowStretchFactors.size(); ++i) {
2654 if (mRowStretchFactors.at(i) <= 0) {
2655 qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:"
2656 << mRowStretchFactors.at(i);
2657 mRowStretchFactors[i] = 1;
2658 }
2659 }
2660 } else
2661 qDebug() << Q_FUNC_INFO
2662 << "Row count not equal to passed stretch factor count:"
2663 << factors;
2664 }
2665
2666 /*!
2667 Sets the gap that is left blank between columns to \a pixels.
2668
2669 \see setRowSpacing
2670 */
2671 void QCPLayoutGrid::setColumnSpacing(int pixels) { mColumnSpacing = pixels; }
2672
2673 /*!
2674 Sets the gap that is left blank between rows to \a pixels.
2675
2676 \see setColumnSpacing
2677 */
2678 void QCPLayoutGrid::setRowSpacing(int pixels) { mRowSpacing = pixels; }
2679
2680 /*!
2681 Expands the layout to have \a newRowCount rows and \a newColumnCount columns.
2682 So the last valid row index will be \a newRowCount-1, the last valid column
2683 index will be \a newColumnCount-1.
2684
2685 If the current column/row count is already larger or equal to \a
2686 newColumnCount/\a newRowCount, this function does nothing in that dimension.
2687
2688 Newly created cells are empty, new rows and columns have the stretch factor 1.
2689
2690 Note that upon a call to \ref addElement, the layout is expanded automatically
2691 to contain the specified row and column, using this function.
2692
2693 \see simplify
2694 */
2695 void QCPLayoutGrid::expandTo(int newRowCount, int newColumnCount) {
2696 // add rows as necessary:
2697 while (rowCount() < newRowCount) {
2698 mElements.append(QList<QCPLayoutElement *>());
2699 mRowStretchFactors.append(1);
2700 }
2701 // go through rows and expand columns as necessary:
2702 int newColCount = qMax(columnCount(), newColumnCount);
2703 for (int i = 0; i < rowCount(); ++i) {
2704 while (mElements.at(i).size() < newColCount) mElements[i].append(0);
2705 }
2706 while (mColumnStretchFactors.size() < newColCount)
2707 mColumnStretchFactors.append(1);
2708 }
2709
2710 /*!
2711 Inserts a new row with empty cells at the row index \a newIndex. Valid values
2712 for \a newIndex range from 0 (inserts a row at the top) to \a rowCount
2713 (appends a row at the bottom).
2714
2715 \see insertColumn
2716 */
2717 void QCPLayoutGrid::insertRow(int newIndex) {
2718 if (mElements.isEmpty() ||
2719 mElements.first()
2720 .isEmpty()) // if grid is completely empty, add first cell
2721 {
2722 expandTo(1, 1);
2723 return;
2724 }
2725
2726 if (newIndex < 0) newIndex = 0;
2727 if (newIndex > rowCount()) newIndex = rowCount();
2728
2729 mRowStretchFactors.insert(newIndex, 1);
2730 QList<QCPLayoutElement *> newRow;
2731 for (int col = 0; col < columnCount(); ++col)
2732 newRow.append((QCPLayoutElement *)0);
2733 mElements.insert(newIndex, newRow);
2734 }
2735
2736 /*!
2737 Inserts a new column with empty cells at the column index \a newIndex. Valid
2738 values for \a newIndex range from 0 (inserts a row at the left) to \a rowCount
2739 (appends a row at the right).
2740
2741 \see insertRow
2742 */
2743 void QCPLayoutGrid::insertColumn(int newIndex) {
2744 if (mElements.isEmpty() ||
2745 mElements.first()
2746 .isEmpty()) // if grid is completely empty, add first cell
2747 {
2748 expandTo(1, 1);
2749 return;
2750 }
2751
2752 if (newIndex < 0) newIndex = 0;
2753 if (newIndex > columnCount()) newIndex = columnCount();
2754
2755 mColumnStretchFactors.insert(newIndex, 1);
2756 for (int row = 0; row < rowCount(); ++row)
2757 mElements[row].insert(newIndex, (QCPLayoutElement *)0);
2758 }
2759
2760 /* inherits documentation from base class */
2761 void QCPLayoutGrid::updateLayout() {
2762 QVector<int> minColWidths, minRowHeights, maxColWidths, maxRowHeights;
2763 getMinimumRowColSizes(&minColWidths, &minRowHeights);
2764 getMaximumRowColSizes(&maxColWidths, &maxRowHeights);
2765
2766 int totalRowSpacing = (rowCount() - 1) * mRowSpacing;
2767 int totalColSpacing = (columnCount() - 1) * mColumnSpacing;
2768 QVector<int> colWidths = getSectionSizes(maxColWidths, minColWidths,
2769 mColumnStretchFactors.toVector(),
2770 mRect.width() - totalColSpacing);
2771 QVector<int> rowHeights = getSectionSizes(maxRowHeights, minRowHeights,
2772 mRowStretchFactors.toVector(),
2773 mRect.height() - totalRowSpacing);
2774
2775 // go through cells and set rects accordingly:
2776 int yOffset = mRect.top();
2777 for (int row = 0; row < rowCount(); ++row) {
2778 if (row > 0) yOffset += rowHeights.at(row - 1) + mRowSpacing;
2779 int xOffset = mRect.left();
2780 for (int col = 0; col < columnCount(); ++col) {
2781 if (col > 0) xOffset += colWidths.at(col - 1) + mColumnSpacing;
2782 if (mElements.at(row).at(col))
2783 mElements.at(row).at(col)->setOuterRect(
2784 QRect(xOffset, yOffset, colWidths.at(col), rowHeights.at(row)));
2785 }
2786 }
2787 }
2788
2789 /* inherits documentation from base class */
2790 int QCPLayoutGrid::elementCount() const { return rowCount() * columnCount(); }
2791
2792 /* inherits documentation from base class */
2793 QCPLayoutElement *QCPLayoutGrid::elementAt(int index) const {
2794 if (index >= 0 && index < elementCount())
2795 return mElements.at(index / columnCount()).at(index % columnCount());
2796 else
2797 return 0;
2798 }
2799
2800 /* inherits documentation from base class */
2801 QCPLayoutElement *QCPLayoutGrid::takeAt(int index) {
2802 if (QCPLayoutElement *el = elementAt(index)) {
2803 releaseElement(el);
2804 mElements[index / columnCount()][index % columnCount()] = 0;
2805 return el;
2806 } else {
2807 qDebug() << Q_FUNC_INFO << "Attempt to take invalid index:" << index;
2808 return 0;
2809 }
2810 }
2811
2812 /* inherits documentation from base class */
2813 bool QCPLayoutGrid::take(QCPLayoutElement *element) {
2814 if (element) {
2815 for (int i = 0; i < elementCount(); ++i) {
2816 if (elementAt(i) == element) {
2817 takeAt(i);
2818 return true;
2819 }
2820 }
2821 qDebug() << Q_FUNC_INFO << "Element not in this layout, couldn't take";
2822 } else
2823 qDebug() << Q_FUNC_INFO << "Can't take null element";
2824 return false;
2825 }
2826
2827 /* inherits documentation from base class */
2828 QList<QCPLayoutElement *> QCPLayoutGrid::elements(bool recursive) const {
2829 QList<QCPLayoutElement *> result;
2830 int colC = columnCount();
2831 int rowC = rowCount();
2832 #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
2833 result.reserve(colC * rowC);
2834 #endif
2835 for (int row = 0; row < rowC; ++row) {
2836 for (int col = 0; col < colC; ++col) {
2837 result.append(mElements.at(row).at(col));
2838 }
2839 }
2840 if (recursive) {
2841 int c = result.size();
2842 for (int i = 0; i < c; ++i) {
2843 if (result.at(i)) result << result.at(i)->elements(recursive);
2844 }
2845 }
2846 return result;
2847 }
2848
2849 /*!
2850 Simplifies the layout by collapsing rows and columns which only contain empty
2851 cells.
2852 */
2853 void QCPLayoutGrid::simplify() {
2854 // remove rows with only empty cells:
2855 for (int row = rowCount() - 1; row >= 0; --row) {
2856 bool hasElements = false;
2857 for (int col = 0; col < columnCount(); ++col) {
2858 if (mElements.at(row).at(col)) {
2859 hasElements = true;
2860 break;
2861 }
2862 }
2863 if (!hasElements) {
2864 mRowStretchFactors.removeAt(row);
2865 mElements.removeAt(row);
2866 if (mElements.isEmpty()) // removed last element, also remove stretch
2867 // factor (wouldn't happen below because also
2868 // columnCount changed to 0 now)
2869 mColumnStretchFactors.clear();
2870 }
2871 }
2872
2873 // remove columns with only empty cells:
2874 for (int col = columnCount() - 1; col >= 0; --col) {
2875 bool hasElements = false;
2876 for (int row = 0; row < rowCount(); ++row) {
2877 if (mElements.at(row).at(col)) {
2878 hasElements = true;
2879 break;
2880 }
2881 }
2882 if (!hasElements) {
2883 mColumnStretchFactors.removeAt(col);
2884 for (int row = 0; row < rowCount(); ++row) mElements[row].removeAt(col);
2885 }
2886 }
2887 }
2888
2889 /* inherits documentation from base class */
2890 QSize QCPLayoutGrid::minimumSizeHint() const {
2891 QVector<int> minColWidths, minRowHeights;
2892 getMinimumRowColSizes(&minColWidths, &minRowHeights);
2893 QSize result(0, 0);
2894 for (int i = 0; i < minColWidths.size(); ++i)
2895 result.rwidth() += minColWidths.at(i);
2896 for (int i = 0; i < minRowHeights.size(); ++i)
2897 result.rheight() += minRowHeights.at(i);
2898 result.rwidth() += qMax(0, columnCount() - 1) * mColumnSpacing +
2899 mMargins.left() + mMargins.right();
2900 result.rheight() += qMax(0, rowCount() - 1) * mRowSpacing + mMargins.top() +
2901 mMargins.bottom();
2902 return result;
2903 }
2904
2905 /* inherits documentation from base class */
2906 QSize QCPLayoutGrid::maximumSizeHint() const {
2907 QVector<int> maxColWidths, maxRowHeights;
2908 getMaximumRowColSizes(&maxColWidths, &maxRowHeights);
2909
2910 QSize result(0, 0);
2911 for (int i = 0; i < maxColWidths.size(); ++i)
2912 result.setWidth(qMin(result.width() + maxColWidths.at(i), QWIDGETSIZE_MAX));
2913 for (int i = 0; i < maxRowHeights.size(); ++i)
2914 result.setHeight(
2915 qMin(result.height() + maxRowHeights.at(i), QWIDGETSIZE_MAX));
2916 result.rwidth() += qMax(0, columnCount() - 1) * mColumnSpacing +
2917 mMargins.left() + mMargins.right();
2918 result.rheight() += qMax(0, rowCount() - 1) * mRowSpacing + mMargins.top() +
2919 mMargins.bottom();
2920 return result;
2921 }
2922
2923 /*! \internal
2924
2925 Places the minimum column widths and row heights into \a minColWidths and \a
2926 minRowHeights respectively.
2927
2928 The minimum height of a row is the largest minimum height of any element in
2929 that row. The minimum width of a column is the largest minimum width of any
2930 element in that column.
2931
2932 This is a helper function for \ref updateLayout.
2933
2934 \see getMaximumRowColSizes
2935 */
2936 void QCPLayoutGrid::getMinimumRowColSizes(QVector<int> *minColWidths,
2937 QVector<int> *minRowHeights) const {
2938 *minColWidths = QVector<int>(columnCount(), 0);
2939 *minRowHeights = QVector<int>(rowCount(), 0);
2940 for (int row = 0; row < rowCount(); ++row) {
2941 for (int col = 0; col < columnCount(); ++col) {
2942 if (mElements.at(row).at(col)) {
2943 QSize minHint = mElements.at(row).at(col)->minimumSizeHint();
2944 QSize min = mElements.at(row).at(col)->minimumSize();
2945 QSize final(min.width() > 0 ? min.width() : minHint.width(),
2946 min.height() > 0 ? min.height() : minHint.height());
2947 if (minColWidths->at(col) < final.width())
2948 (*minColWidths)[col] = final.width();
2949 if (minRowHeights->at(row) < final.height())
2950 (*minRowHeights)[row] = final.height();
2951 }
2952 }
2953 }
2954 }
2955
2956 /*! \internal
2957
2958 Places the maximum column widths and row heights into \a maxColWidths and \a
2959 maxRowHeights respectively.
2960
2961 The maximum height of a row is the smallest maximum height of any element in
2962 that row. The maximum width of a column is the smallest maximum width of any
2963 element in that column.
2964
2965 This is a helper function for \ref updateLayout.
2966
2967 \see getMinimumRowColSizes
2968 */
2969 void QCPLayoutGrid::getMaximumRowColSizes(QVector<int> *maxColWidths,
2970 QVector<int> *maxRowHeights) const {
2971 *maxColWidths = QVector<int>(columnCount(), QWIDGETSIZE_MAX);
2972 *maxRowHeights = QVector<int>(rowCount(), QWIDGETSIZE_MAX);
2973 for (int row = 0; row < rowCount(); ++row) {
2974 for (int col = 0; col < columnCount(); ++col) {
2975 if (mElements.at(row).at(col)) {
2976 QSize maxHint = mElements.at(row).at(col)->maximumSizeHint();
2977 QSize max = mElements.at(row).at(col)->maximumSize();
2978 QSize final(
2979 max.width() < QWIDGETSIZE_MAX ? max.width() : maxHint.width(),
2980 max.height() < QWIDGETSIZE_MAX ? max.height() : maxHint.height());
2981 if (maxColWidths->at(col) > final.width())
2982 (*maxColWidths)[col] = final.width();
2983 if (maxRowHeights->at(row) > final.height())
2984 (*maxRowHeights)[row] = final.height();
2985 }
2986 }
2987 }
2988 }
2989
2990 ////////////////////////////////////////////////////////////////////////////////////////////////////
2991 //////////////////// QCPLayoutInset
2992 ////////////////////////////////////////////////////////////////////////////////////////////////////
2993 /*! \class QCPLayoutInset
2994 \brief A layout that places child elements aligned to the border or
2995 arbitrarily positioned
2996
2997 Elements are placed either aligned to the border or at arbitrary position in
2998 the area of the layout. Which placement applies is controlled with the \ref
2999 InsetPlacement (\ref setInsetPlacement).
3000
3001 Elements are added via \ref addElement(QCPLayoutElement *element,
3002 Qt::Alignment alignment) or addElement(QCPLayoutElement *element, const QRectF
3003 &rect). If the first method is used, the inset placement will default to \ref
3004 ipBorderAligned and the element will be aligned according to the \a alignment
3005 parameter. The second method defaults to \ref ipFree and allows placing
3006 elements at arbitrary position and size, defined by \a rect.
3007
3008 The alignment or rect can be set via \ref setInsetAlignment or \ref
3009 setInsetRect, respectively.
3010
3011 This is the layout that every QCPAxisRect has as \ref
3012 QCPAxisRect::insetLayout.
3013 */
3014
3015 /* start documentation of inline functions */
3016
3017 /*! \fn virtual void QCPLayoutInset::simplify()
3018
3019 The QCPInsetLayout does not need simplification since it can never have empty
3020 cells due to its linear index structure. This method does nothing.
3021 */
3022
3023 /* end documentation of inline functions */
3024
3025 /*!
3026 Creates an instance of QCPLayoutInset and sets default values.
3027 */
3028 QCPLayoutInset::QCPLayoutInset() {}
3029
3030 QCPLayoutInset::~QCPLayoutInset() {
3031 // clear all child layout elements. This is important because only the
3032 // specific layouts know how to handle removing elements (clear calls virtual
3033 // removeAt method to do that).
3034 clear();
3035 }
3036
3037 /*!
3038 Returns the placement type of the element with the specified \a index.
3039 */
3040 QCPLayoutInset::InsetPlacement QCPLayoutInset::insetPlacement(int index) const {
3041 if (elementAt(index))
3042 return mInsetPlacement.at(index);
3043 else {
3044 qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
3045 return ipFree;
3046 }
3047 }
3048
3049 /*!
3050 Returns the alignment of the element with the specified \a index. The
3051 alignment only has a meaning, if the inset placement (\ref setInsetPlacement)
3052 is \ref ipBorderAligned.
3053 */
3054 Qt::Alignment QCPLayoutInset::insetAlignment(int index) const {
3055 if (elementAt(index))
3056 return mInsetAlignment.at(index);
3057 else {
3058 qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
3059 return 0;
3060 }
3061 }
3062
3063 /*!
3064 Returns the rect of the element with the specified \a index. The rect only has
3065 a meaning, if the inset placement (\ref setInsetPlacement) is \ref ipFree.
3066 */
3067 QRectF QCPLayoutInset::insetRect(int index) const {
3068 if (elementAt(index))
3069 return mInsetRect.at(index);
3070 else {
3071 qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
3072 return QRectF();
3073 }
3074 }
3075
3076 /*!
3077 Sets the inset placement type of the element with the specified \a index to \a
3078 placement.
3079
3080 \see InsetPlacement
3081 */
3082 void QCPLayoutInset::setInsetPlacement(
3083 int index, QCPLayoutInset::InsetPlacement placement) {
3084 if (elementAt(index))
3085 mInsetPlacement[index] = placement;
3086 else
3087 qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
3088 }
3089
3090 /*!
3091 If the inset placement (\ref setInsetPlacement) is \ref ipBorderAligned, this
3092 function is used to set the alignment of the element with the specified \a
3093 index to \a alignment.
3094
3095 \a alignment is an or combination of the following alignment flags:
3096 Qt::AlignLeft, Qt::AlignHCenter, Qt::AlighRight, Qt::AlignTop,
3097 Qt::AlignVCenter, Qt::AlignBottom. Any other alignment flags will be ignored.
3098 */
3099 void QCPLayoutInset::setInsetAlignment(int index, Qt::Alignment alignment) {
3100 if (elementAt(index))
3101 mInsetAlignment[index] = alignment;
3102 else
3103 qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
3104 }
3105
3106 /*!
3107 If the inset placement (\ref setInsetPlacement) is \ref ipFree, this function
3108 is used to set the position and size of the element with the specified \a
3109 index to \a rect.
3110
3111 \a rect is given in fractions of the whole inset layout rect. So an inset with
3112 rect (0, 0, 1, 1) will span the entire layout. An inset with rect (0.6, 0.1,
3113 0.35, 0.35) will be in the top right corner of the layout, with 35% width and
3114 height of the parent layout.
3115
3116 Note that the minimum and maximum sizes of the embedded element (\ref
3117 QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize) are
3118 enforced.
3119 */
3120 void QCPLayoutInset::setInsetRect(int index, const QRectF &rect) {
3121 if (elementAt(index))
3122 mInsetRect[index] = rect;
3123 else
3124 qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
3125 }
3126
3127 /* inherits documentation from base class */
3128 void QCPLayoutInset::updateLayout() {
3129 for (int i = 0; i < mElements.size(); ++i) {
3130 QRect insetRect;
3131 QSize finalMinSize, finalMaxSize;
3132 QSize minSizeHint = mElements.at(i)->minimumSizeHint();
3133 QSize maxSizeHint = mElements.at(i)->maximumSizeHint();
3134 finalMinSize.setWidth(mElements.at(i)->minimumSize().width() > 0
3135 ? mElements.at(i)->minimumSize().width()
3136 : minSizeHint.width());
3137 finalMinSize.setHeight(mElements.at(i)->minimumSize().height() > 0
3138 ? mElements.at(i)->minimumSize().height()
3139 : minSizeHint.height());
3140 finalMaxSize.setWidth(mElements.at(i)->maximumSize().width() <
3141 QWIDGETSIZE_MAX
3142 ? mElements.at(i)->maximumSize().width()
3143 : maxSizeHint.width());
3144 finalMaxSize.setHeight(mElements.at(i)->maximumSize().height() <
3145 QWIDGETSIZE_MAX
3146 ? mElements.at(i)->maximumSize().height()
3147 : maxSizeHint.height());
3148 if (mInsetPlacement.at(i) == ipFree) {
3149 insetRect = QRect(rect().x() + rect().width() * mInsetRect.at(i).x(),
3150 rect().y() + rect().height() * mInsetRect.at(i).y(),
3151 rect().width() * mInsetRect.at(i).width(),
3152 rect().height() * mInsetRect.at(i).height());
3153 if (insetRect.size().width() < finalMinSize.width())
3154 insetRect.setWidth(finalMinSize.width());
3155 if (insetRect.size().height() < finalMinSize.height())
3156 insetRect.setHeight(finalMinSize.height());
3157 if (insetRect.size().width() > finalMaxSize.width())
3158 insetRect.setWidth(finalMaxSize.width());
3159 if (insetRect.size().height() > finalMaxSize.height())
3160 insetRect.setHeight(finalMaxSize.height());
3161 } else if (mInsetPlacement.at(i) == ipBorderAligned) {
3162 insetRect.setSize(finalMinSize);
3163 Qt::Alignment al = mInsetAlignment.at(i);
3164 if (al.testFlag(Qt::AlignLeft))
3165 insetRect.moveLeft(rect().x());
3166 else if (al.testFlag(Qt::AlignRight))
3167 insetRect.moveRight(rect().x() + rect().width());
3168 else
3169 insetRect.moveLeft(rect().x() + rect().width() * 0.5 -
3170 finalMinSize.width() *
3171 0.5); // default to Qt::AlignHCenter
3172 if (al.testFlag(Qt::AlignTop))
3173 insetRect.moveTop(rect().y());
3174 else if (al.testFlag(Qt::AlignBottom))
3175 insetRect.moveBottom(rect().y() + rect().height());
3176 else
3177 insetRect.moveTop(rect().y() + rect().height() * 0.5 -
3178 finalMinSize.height() *
3179 0.5); // default to Qt::AlignVCenter
3180 }
3181 mElements.at(i)->setOuterRect(insetRect);
3182 }
3183 }
3184
3185 /* inherits documentation from base class */
3186 int QCPLayoutInset::elementCount() const { return mElements.size(); }
3187
3188 /* inherits documentation from base class */
3189 QCPLayoutElement *QCPLayoutInset::elementAt(int index) const {
3190 if (index >= 0 && index < mElements.size())
3191 return mElements.at(index);
3192 else
3193 return 0;
3194 }
3195
3196 /* inherits documentation from base class */
3197 QCPLayoutElement *QCPLayoutInset::takeAt(int index) {
3198 if (QCPLayoutElement *el = elementAt(index)) {
3199 releaseElement(el);
3200 mElements.removeAt(index);
3201 mInsetPlacement.removeAt(index);
3202 mInsetAlignment.removeAt(index);
3203 mInsetRect.removeAt(index);
3204 return el;
3205 } else {
3206 qDebug() << Q_FUNC_INFO << "Attempt to take invalid index:" << index;
3207 return 0;
3208 }
3209 }
3210
3211 /* inherits documentation from base class */
3212 bool QCPLayoutInset::take(QCPLayoutElement *element) {
3213 if (element) {
3214 for (int i = 0; i < elementCount(); ++i) {
3215 if (elementAt(i) == element) {
3216 takeAt(i);
3217 return true;
3218 }
3219 }
3220 qDebug() << Q_FUNC_INFO << "Element not in this layout, couldn't take";
3221 } else
3222 qDebug() << Q_FUNC_INFO << "Can't take null element";
3223 return false;
3224 }
3225
3226 /*!
3227 The inset layout is sensitive to events only at areas where its (visible)
3228 child elements are sensitive. If the selectTest method of any of the child
3229 elements returns a positive number for \a pos, this method returns a value
3230 corresponding to 0.99 times the parent plot's selection tolerance. The inset
3231 layout is not selectable itself by default. So if \a onlySelectable is true,
3232 -1.0 is returned.
3233
3234 See \ref QCPLayerable::selectTest for a general explanation of this virtual
3235 method.
3236 */
3237 double QCPLayoutInset::selectTest(const QPointF &pos, bool onlySelectable,
3238 QVariant *details) const {
3239 Q_UNUSED(details)
3240 if (onlySelectable) return -1;
3241
3242 for (int i = 0; i < mElements.size(); ++i) {
3243 // inset layout shall only return positive selectTest, if actually an inset
3244 // object is at pos else it would block the entire underlying QCPAxisRect
3245 // with its surface.
3246 if (mElements.at(i)->realVisibility() &&
3247 mElements.at(i)->selectTest(pos, onlySelectable) >= 0)
3248 return mParentPlot->selectionTolerance() * 0.99;
3249 }
3250 return -1;
3251 }
3252
3253 /*!
3254 Adds the specified \a element to the layout as an inset aligned at the border
3255 (\ref setInsetAlignment is initialized with \ref ipBorderAligned). The
3256 alignment is set to \a alignment.
3257
3258 \a alignment is an or combination of the following alignment flags:
3259 Qt::AlignLeft, Qt::AlignHCenter, Qt::AlighRight, Qt::AlignTop,
3260 Qt::AlignVCenter, Qt::AlignBottom. Any other alignment flags will be ignored.
3261
3262 \see addElement(QCPLayoutElement *element, const QRectF &rect)
3263 */
3264 void QCPLayoutInset::addElement(QCPLayoutElement *element,
3265 Qt::Alignment alignment) {
3266 if (element) {
3267 if (element->layout()) // remove from old layout first
3268 element->layout()->take(element);
3269 mElements.append(element);
3270 mInsetPlacement.append(ipBorderAligned);
3271 mInsetAlignment.append(alignment);
3272 mInsetRect.append(QRectF(0.6, 0.6, 0.4, 0.4));
3273 adoptElement(element);
3274 } else
3275 qDebug() << Q_FUNC_INFO << "Can't add null element";
3276 }
3277
3278 /*!
3279 Adds the specified \a element to the layout as an inset with free
3280 positioning/sizing (\ref setInsetAlignment is initialized with \ref ipFree).
3281 The position and size is set to \a rect.
3282
3283 \a rect is given in fractions of the whole inset layout rect. So an inset with
3284 rect (0, 0, 1, 1) will span the entire layout. An inset with rect (0.6, 0.1,
3285 0.35, 0.35) will be in the top right corner of the layout, with 35% width and
3286 height of the parent layout.
3287
3288 \see addElement(QCPLayoutElement *element, Qt::Alignment alignment)
3289 */
3290 void QCPLayoutInset::addElement(QCPLayoutElement *element, const QRectF &rect) {
3291 if (element) {
3292 if (element->layout()) // remove from old layout first
3293 element->layout()->take(element);
3294 mElements.append(element);
3295 mInsetPlacement.append(ipFree);
3296 mInsetAlignment.append(Qt::AlignRight | Qt::AlignTop);
3297 mInsetRect.append(rect);
3298 adoptElement(element);
3299 } else
3300 qDebug() << Q_FUNC_INFO << "Can't add null element";
3301 }
3302
3303 ////////////////////////////////////////////////////////////////////////////////////////////////////
3304 //////////////////// QCPLineEnding
3305 ////////////////////////////////////////////////////////////////////////////////////////////////////
3306
3307 /*! \class QCPLineEnding
3308 \brief Handles the different ending decorations for line-like items
3309
3310 \image html QCPLineEnding.png "The various ending styles currently supported"
3311
3312 For every ending a line-like item has, an instance of this class exists. For
3313 example, QCPItemLine has two endings which can be set with
3314 QCPItemLine::setHead and QCPItemLine::setTail.
3315
3316 The styles themselves are defined via the enum QCPLineEnding::EndingStyle.
3317 Most decorations can be modified regarding width and length, see \ref setWidth
3318 and \ref setLength. The direction of the ending decoration (e.g. direction an
3319 arrow is pointing) is controlled by the line-like item. For example, when both
3320 endings of a QCPItemLine are set to be arrows, they will point to opposite
3321 directions, e.g. "outward". This can be changed by \ref setInverted, which
3322 would make the respective arrow point inward.
3323
3324 Note that due to the overloaded QCPLineEnding constructor, you may directly
3325 specify a QCPLineEnding::EndingStyle where actually a QCPLineEnding is
3326 expected, e.g. \snippet documentation/doc-code-snippets/mainwindow.cpp
3327 qcplineending-sethead
3328 */
3329
3330 /*!
3331 Creates a QCPLineEnding instance with default values (style \ref esNone).
3332 */
3333 QCPLineEnding::QCPLineEnding()
3334 : mStyle(esNone), mWidth(8), mLength(10), mInverted(false) {}
3335
3336 /*!
3337 Creates a QCPLineEnding instance with the specified values.
3338 */
3339 QCPLineEnding::QCPLineEnding(QCPLineEnding::EndingStyle style, double width,
3340 double length, bool inverted)
3341 : mStyle(style), mWidth(width), mLength(length), mInverted(inverted) {}
3342
3343 /*!
3344 Sets the style of the ending decoration.
3345 */
3346 void QCPLineEnding::setStyle(QCPLineEnding::EndingStyle style) {
3347 mStyle = style;
3348 }
3349
3350 /*!
3351 Sets the width of the ending decoration, if the style supports it. On arrows,
3352 for example, the width defines the size perpendicular to the arrow's pointing
3353 direction.
3354
3355 \see setLength
3356 */
3357 void QCPLineEnding::setWidth(double width) { mWidth = width; }
3358
3359 /*!
3360 Sets the length of the ending decoration, if the style supports it. On arrows,
3361 for example, the length defines the size in pointing direction.
3362
3363 \see setWidth
3364 */
3365 void QCPLineEnding::setLength(double length) { mLength = length; }
3366
3367 /*!
3368 Sets whether the ending decoration shall be inverted. For example, an arrow
3369 decoration will point inward when \a inverted is set to true.
3370
3371 Note that also the \a width direction is inverted. For symmetrical ending
3372 styles like arrows or discs, this doesn't make a difference. However,
3373 asymmetric styles like \ref esHalfBar are affected by it, which can be used to
3374 control to which side the half bar points to.
3375 */
3376 void QCPLineEnding::setInverted(bool inverted) { mInverted = inverted; }
3377
3378 /*! \internal
3379
3380 Returns the maximum pixel radius the ending decoration might cover, starting
3381 from the position the decoration is drawn at (typically a line ending/\ref
3382 QCPItemPosition of an item).
3383
3384 This is relevant for clipping. Only omit painting of the decoration when the
3385 position where the decoration is supposed to be drawn is farther away from the
3386 clipping rect than the returned distance.
3387 */
3388 double QCPLineEnding::boundingDistance() const {
3389 switch (mStyle) {
3390 case esNone:
3391 return 0;
3392
3393 case esFlatArrow:
3394 case esSpikeArrow:
3395 case esLineArrow:
3396 case esSkewedBar:
3397 return qSqrt(mWidth * mWidth +
3398 mLength * mLength); // items that have width and length
3399
3400 case esDisc:
3401 case esSquare:
3402 case esDiamond:
3403 case esBar:
3404 case esHalfBar:
3405 return mWidth * 1.42; // items that only have a width -> width*sqrt(2)
3406 }
3407 return 0;
3408 }
3409
3410 /*!
3411 Starting from the origin of this line ending (which is style specific),
3412 returns the length covered by the line ending symbol, in backward direction.
3413
3414 For example, the \ref esSpikeArrow has a shorter real length than a \ref
3415 esFlatArrow, even if both have the same \ref setLength value, because the
3416 spike arrow has an inward curved back, which reduces the length along its
3417 center axis (the drawing origin for arrows is at the tip).
3418
3419 This function is used for precise, style specific placement of line endings,
3420 for example in QCPAxes.
3421 */
3422 double QCPLineEnding::realLength() const {
3423 switch (mStyle) {
3424 case esNone:
3425 case esLineArrow:
3426 case esSkewedBar:
3427 case esBar:
3428 case esHalfBar:
3429 return 0;
3430
3431 case esFlatArrow:
3432 return mLength;
3433
3434 case esDisc:
3435 case esSquare:
3436 case esDiamond:
3437 return mWidth * 0.5;
3438
3439 case esSpikeArrow:
3440 return mLength * 0.8;
3441 }
3442 return 0;
3443 }
3444
3445 /*! \internal
3446
3447 Draws the line ending with the specified \a painter at the position \a pos.
3448 The direction of the line ending is controlled with \a dir.
3449 */
3450 void QCPLineEnding::draw(QCPPainter *painter, const QVector2D &pos,
3451 const QVector2D &dir) const {
3452 if (mStyle == esNone) return;
3453
3454 QVector2D lengthVec(dir.normalized());
3455 if (lengthVec.isNull()) lengthVec = QVector2D(1, 0);
3456 QVector2D widthVec(-lengthVec.y(), lengthVec.x());
3457 lengthVec *= (float)(mLength * (mInverted ? -1 : 1));
3458 widthVec *= (float)(mWidth * 0.5 * (mInverted ? -1 : 1));
3459
3460 QPen penBackup = painter->pen();
3461 QBrush brushBackup = painter->brush();
3462 QPen miterPen = penBackup;
3463 miterPen.setJoinStyle(Qt::MiterJoin); // to make arrow heads spikey
3464 QBrush brush(painter->pen().color(), Qt::SolidPattern);
3465 switch (mStyle) {
3466 case esNone:
3467 break;
3468 case esFlatArrow: {
3469 QPointF points[3] = {pos.toPointF(),
3470 (pos - lengthVec + widthVec).toPointF(),
3471 (pos - lengthVec - widthVec).toPointF()};
3472 painter->setPen(miterPen);
3473 painter->setBrush(brush);
3474 painter->drawConvexPolygon(points, 3);
3475 painter->setBrush(brushBackup);
3476 painter->setPen(penBackup);
3477 break;
3478 }
3479 case esSpikeArrow: {
3480 QPointF points[4] = {pos.toPointF(),
3481 (pos - lengthVec + widthVec).toPointF(),
3482 (pos - lengthVec * 0.8f).toPointF(),
3483 (pos - lengthVec - widthVec).toPointF()};
3484 painter->setPen(miterPen);
3485 painter->setBrush(brush);
3486 painter->drawConvexPolygon(points, 4);
3487 painter->setBrush(brushBackup);
3488 painter->setPen(penBackup);
3489 break;
3490 }
3491 case esLineArrow: {
3492 QPointF points[3] = {(pos - lengthVec + widthVec).toPointF(),
3493 pos.toPointF(),
3494 (pos - lengthVec - widthVec).toPointF()};
3495 painter->setPen(miterPen);
3496 painter->drawPolyline(points, 3);
3497 painter->setPen(penBackup);
3498 break;
3499 }
3500 case esDisc: {
3501 painter->setBrush(brush);
3502 painter->drawEllipse(pos.toPointF(), mWidth * 0.5, mWidth * 0.5);
3503 painter->setBrush(brushBackup);
3504 break;
3505 }
3506 case esSquare: {
3507 QVector2D widthVecPerp(-widthVec.y(), widthVec.x());
3508 QPointF points[4] = {(pos - widthVecPerp + widthVec).toPointF(),
3509 (pos - widthVecPerp - widthVec).toPointF(),
3510 (pos + widthVecPerp - widthVec).toPointF(),
3511 (pos + widthVecPerp + widthVec).toPointF()};
3512 painter->setPen(miterPen);
3513 painter->setBrush(brush);
3514 painter->drawConvexPolygon(points, 4);
3515 painter->setBrush(brushBackup);
3516 painter->setPen(penBackup);
3517 break;
3518 }
3519 case esDiamond: {
3520 QVector2D widthVecPerp(-widthVec.y(), widthVec.x());
3521 QPointF points[4] = {
3522 (pos - widthVecPerp).toPointF(), (pos - widthVec).toPointF(),
3523 (pos + widthVecPerp).toPointF(), (pos + widthVec).toPointF()};
3524 painter->setPen(miterPen);
3525 painter->setBrush(brush);
3526 painter->drawConvexPolygon(points, 4);
3527 painter->setBrush(brushBackup);
3528 painter->setPen(penBackup);
3529 break;
3530 }
3531 case esBar: {
3532 painter->drawLine((pos + widthVec).toPointF(),
3533 (pos - widthVec).toPointF());
3534 break;
3535 }
3536 case esHalfBar: {
3537 painter->drawLine((pos + widthVec).toPointF(), pos.toPointF());
3538 break;
3539 }
3540 case esSkewedBar: {
3541 if (qFuzzyIsNull(painter->pen().widthF()) &&
3542 !painter->modes().testFlag(QCPPainter::pmNonCosmetic)) {
3543 // if drawing with cosmetic pen (perfectly thin stroke, happens only in
3544 // vector exports), draw bar exactly on tip of line
3545 painter->drawLine(
3546 (pos + widthVec + lengthVec * 0.2f * (mInverted ? -1 : 1))
3547 .toPointF(),
3548 (pos - widthVec - lengthVec * 0.2f * (mInverted ? -1 : 1))
3549 .toPointF());
3550 } else {
3551 // if drawing with thick (non-cosmetic) pen, shift bar a little in line
3552 // direction to prevent line from sticking through bar slightly
3553 painter->drawLine(
3554 (pos + widthVec + lengthVec * 0.2f * (mInverted ? -1 : 1) +
3555 dir.normalized() * qMax(1.0f, (float)painter->pen().widthF()) *
3556 0.5f)
3557 .toPointF(),
3558 (pos - widthVec - lengthVec * 0.2f * (mInverted ? -1 : 1) +
3559 dir.normalized() * qMax(1.0f, (float)painter->pen().widthF()) *
3560 0.5f)
3561 .toPointF());
3562 }
3563 break;
3564 }
3565 }
3566 }
3567
3568 /*! \internal
3569 \overload
3570
3571 Draws the line ending. The direction is controlled with the \a angle parameter
3572 in radians.
3573 */
3574 void QCPLineEnding::draw(QCPPainter *painter, const QVector2D &pos,
3575 double angle) const {
3576 draw(painter, pos, QVector2D(qCos(angle), qSin(angle)));
3577 }
3578
3579 ////////////////////////////////////////////////////////////////////////////////////////////////////
3580 //////////////////// QCPGrid
3581 ////////////////////////////////////////////////////////////////////////////////////////////////////
3582
3583 /*! \class QCPGrid
3584 \brief Responsible for drawing the grid of a QCPAxis.
3585
3586 This class is tightly bound to QCPAxis. Every axis owns a grid instance and
3587 uses it to draw the grid lines, sub grid lines and zero-line. You can interact
3588 with the grid of an axis via \ref QCPAxis::grid. Normally, you don't need to
3589 create an instance of QCPGrid yourself.
3590
3591 The axis and grid drawing was split into two classes to allow them to be
3592 placed on different layers (both QCPAxis and QCPGrid inherit from
3593 QCPLayerable). Thus it is possible to have the grid in the background and the
3594 axes in the foreground, and any plottables/items in between. This described
3595 situation is the default setup, see the QCPLayer documentation.
3596 */
3597
3598 /*!
3599 Creates a QCPGrid instance and sets default values.
3600
3601 You shouldn't instantiate grids on their own, since every QCPAxis brings its
3602 own QCPGrid.
3603 */
3604 QCPGrid::QCPGrid(QCPAxis *parentAxis)
3605 : QCPLayerable(parentAxis->parentPlot(), QString(), parentAxis),
3606 mParentAxis(parentAxis) {
3607 // warning: this is called in QCPAxis constructor, so parentAxis members
3608 // should not be accessed/called
3609 setParent(parentAxis);
3610 setPen(QPen(QColor(200, 200, 200), 0, Qt::DotLine));
3611 setSubGridPen(QPen(QColor(220, 220, 220), 0, Qt::DotLine));
3612 setZeroLinePen(QPen(QColor(200, 200, 200), 0, Qt::SolidLine));
3613 setSubGridVisible(false);
3614 setAntialiased(false);
3615 setAntialiasedSubGrid(false);
3616 setAntialiasedZeroLine(false);
3617 }
3618
3619 /*!
3620 Sets whether grid lines at sub tick marks are drawn.
3621
3622 \see setSubGridPen
3623 */
3624 void QCPGrid::setSubGridVisible(bool visible) { mSubGridVisible = visible; }
3625
3626 /*!
3627 Sets whether sub grid lines are drawn antialiased.
3628 */
3629 void QCPGrid::setAntialiasedSubGrid(bool enabled) {
3630 mAntialiasedSubGrid = enabled;
3631 }
3632
3633 /*!
3634 Sets whether zero lines are drawn antialiased.
3635 */
3636 void QCPGrid::setAntialiasedZeroLine(bool enabled) {
3637 mAntialiasedZeroLine = enabled;
3638 }
3639
3640 /*!
3641 Sets the pen with which (major) grid lines are drawn.
3642 */
3643 void QCPGrid::setPen(const QPen &pen) { mPen = pen; }
3644
3645 /*!
3646 Sets the pen with which sub grid lines are drawn.
3647 */
3648 void QCPGrid::setSubGridPen(const QPen &pen) { mSubGridPen = pen; }
3649
3650 /*!
3651 Sets the pen with which zero lines are drawn.
3652
3653 Zero lines are lines at value coordinate 0 which may be drawn with a different
3654 pen than other grid lines. To disable zero lines and just draw normal grid
3655 lines at zero, set \a pen to Qt::NoPen.
3656 */
3657 void QCPGrid::setZeroLinePen(const QPen &pen) { mZeroLinePen = pen; }
3658
3659 /*! \internal
3660
3661 A convenience function to easily set the QPainter::Antialiased hint on the
3662 provided \a painter before drawing the major grid lines.
3663
3664 This is the antialiasing state the painter passed to the \ref draw method is
3665 in by default.
3666
3667 This function takes into account the local setting of the antialiasing flag as
3668 well as the overrides set with \ref QCustomPlot::setAntialiasedElements and
3669 \ref QCustomPlot::setNotAntialiasedElements.
3670
3671 \see setAntialiased
3672 */
3673 void QCPGrid::applyDefaultAntialiasingHint(QCPPainter *painter) const {
3674 applyAntialiasingHint(painter, mAntialiased, QCP::aeGrid);
3675 }
3676
3677 /*! \internal
3678
3679 Draws grid lines and sub grid lines at the positions of (sub) ticks of the
3680 parent axis, spanning over the complete axis rect. Also draws the zero line,
3681 if appropriate (\ref setZeroLinePen).
3682 */
3683 void QCPGrid::draw(QCPPainter *painter) {
3684 if (!mParentAxis) {
3685 qDebug() << Q_FUNC_INFO << "invalid parent axis";
3686 return;
3687 }
3688
3689 if (mSubGridVisible) drawSubGridLines(painter);
3690 drawGridLines(painter);
3691 }
3692
3693 /*! \internal
3694
3695 Draws the main grid lines and possibly a zero line with the specified painter.
3696
3697 This is a helper function called by \ref draw.
3698 */
3699 void QCPGrid::drawGridLines(QCPPainter *painter) const {
3700 if (!mParentAxis) {
3701 qDebug() << Q_FUNC_INFO << "invalid parent axis";
3702 return;
3703 }
3704
3705 int lowTick = mParentAxis->mLowestVisibleTick;
3706 int highTick = mParentAxis->mHighestVisibleTick;
3707 double t; // helper variable, result of coordinate-to-pixel transforms
3708 if (mParentAxis->orientation() == Qt::Horizontal) {
3709 // draw zeroline:
3710 int zeroLineIndex = -1;
3711 if (mZeroLinePen.style() != Qt::NoPen && mParentAxis->mRange.lower < 0 &&
3712 mParentAxis->mRange.upper > 0) {
3713 applyAntialiasingHint(painter, mAntialiasedZeroLine, QCP::aeZeroLine);
3714 painter->setPen(mZeroLinePen);
3715 double epsilon =
3716 mParentAxis->range().size() * 1E-6; // for comparing double to zero
3717 for (int i = lowTick; i <= highTick; ++i) {
3718 if (qAbs(mParentAxis->mTickVector.at(i)) < epsilon) {
3719 zeroLineIndex = i;
3720 t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // x
3721 painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t,
3722 mParentAxis->mAxisRect->top()));
3723 break;
3724 }
3725 }
3726 }
3727 // draw grid lines:
3728 applyDefaultAntialiasingHint(painter);
3729 painter->setPen(mPen);
3730 for (int i = lowTick; i <= highTick; ++i) {
3731 if (i == zeroLineIndex)
3732 continue; // don't draw a gridline on top of the zeroline
3733 t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // x
3734 painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t,
3735 mParentAxis->mAxisRect->top()));
3736 }
3737 } else {
3738 // draw zeroline:
3739 int zeroLineIndex = -1;
3740 if (mZeroLinePen.style() != Qt::NoPen && mParentAxis->mRange.lower < 0 &&
3741 mParentAxis->mRange.upper > 0) {
3742 applyAntialiasingHint(painter, mAntialiasedZeroLine, QCP::aeZeroLine);
3743 painter->setPen(mZeroLinePen);
3744 double epsilon =
3745 mParentAxis->mRange.size() * 1E-6; // for comparing double to zero
3746 for (int i = lowTick; i <= highTick; ++i) {
3747 if (qAbs(mParentAxis->mTickVector.at(i)) < epsilon) {
3748 zeroLineIndex = i;
3749 t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // y
3750 painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t,
3751 mParentAxis->mAxisRect->right(), t));
3752 break;
3753 }
3754 }
3755 }
3756 // draw grid lines:
3757 applyDefaultAntialiasingHint(painter);
3758 painter->setPen(mPen);
3759 for (int i = lowTick; i <= highTick; ++i) {
3760 if (i == zeroLineIndex)
3761 continue; // don't draw a gridline on top of the zeroline
3762 t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // y
3763 painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t,
3764 mParentAxis->mAxisRect->right(), t));
3765 }
3766 }
3767 }
3768
3769 /*! \internal
3770
3771 Draws the sub grid lines with the specified painter.
3772
3773 This is a helper function called by \ref draw.
3774 */
3775 void QCPGrid::drawSubGridLines(QCPPainter *painter) const {
3776 if (!mParentAxis) {
3777 qDebug() << Q_FUNC_INFO << "invalid parent axis";
3778 return;
3779 }
3780
3781 applyAntialiasingHint(painter, mAntialiasedSubGrid, QCP::aeSubGrid);
3782 double t; // helper variable, result of coordinate-to-pixel transforms
3783 painter->setPen(mSubGridPen);
3784 if (mParentAxis->orientation() == Qt::Horizontal) {
3785 for (int i = 0; i < mParentAxis->mSubTickVector.size(); ++i) {
3786 t = mParentAxis->coordToPixel(mParentAxis->mSubTickVector.at(i)); // x
3787 painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t,
3788 mParentAxis->mAxisRect->top()));
3789 }
3790 } else {
3791 for (int i = 0; i < mParentAxis->mSubTickVector.size(); ++i) {
3792 t = mParentAxis->coordToPixel(mParentAxis->mSubTickVector.at(i)); // y
3793 painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t,
3794 mParentAxis->mAxisRect->right(), t));
3795 }
3796 }
3797 }
3798
3799 ////////////////////////////////////////////////////////////////////////////////////////////////////
3800 //////////////////// QCPAxis
3801 ////////////////////////////////////////////////////////////////////////////////////////////////////
3802
3803 /*! \class QCPAxis
3804 \brief Manages a single axis inside a QCustomPlot.
3805
3806 Usually doesn't need to be instantiated externally. Access %QCustomPlot's
3807 default four axes via QCustomPlot::xAxis (bottom), QCustomPlot::yAxis (left),
3808 QCustomPlot::xAxis2 (top) and QCustomPlot::yAxis2 (right).
3809
3810 Axes are always part of an axis rect, see QCPAxisRect.
3811 \image html AxisNamesOverview.png
3812 <center>Naming convention of axis parts</center>
3813 \n
3814
3815 \image html AxisRectSpacingOverview.png
3816 <center>Overview of the spacings and paddings that define the geometry of an
3817 axis. The dashed gray line on the left represents the QCustomPlot widget
3818 border.</center>
3819
3820 */
3821
3822 /* start of documentation of inline functions */
3823
3824 /*! \fn Qt::Orientation QCPAxis::orientation() const
3825
3826 Returns the orientation of this axis. The axis orientation (horizontal or
3827 vertical) is deduced from the axis type (left, top, right or bottom).
3828
3829 \see orientation(AxisType type)
3830 */
3831
3832 /*! \fn QCPGrid *QCPAxis::grid() const
3833
3834 Returns the \ref QCPGrid instance belonging to this axis. Access it to set
3835 details about the way the grid is displayed.
3836 */
3837
3838 /*! \fn static Qt::Orientation QCPAxis::orientation(AxisType type)
3839
3840 Returns the orientation of the specified axis type
3841
3842 \see orientation()
3843 */
3844
3845 /* end of documentation of inline functions */
3846 /* start of documentation of signals */
3847
3848 /*! \fn void QCPAxis::ticksRequest()
3849
3850 This signal is emitted when \ref setAutoTicks is false and the axis is about
3851 to generate tick labels for a replot.
3852
3853 Modifying the tick positions can be done with \ref setTickVector. If you also
3854 want to control the tick labels, set \ref setAutoTickLabels to false and also
3855 provide the labels with \ref setTickVectorLabels.
3856
3857 If you only want static ticks you probably don't need this signal, since you
3858 can just set the tick vector (and possibly tick label vector) once. However,
3859 if you want to provide ticks (and maybe labels) dynamically, e.g. depending on
3860 the current axis range, connect a slot to this signal and set the
3861 vector/vectors there.
3862 */
3863
3864 /*! \fn void QCPAxis::rangeChanged(const QCPRange &newRange)
3865
3866 This signal is emitted when the range of this axis has changed. You can
3867 connect it to the \ref setRange slot of another axis to communicate the new
3868 range to the other axis, in order for it to be synchronized.
3869
3870 You may also manipulate/correct the range with \ref setRange in a slot
3871 connected to this signal. This is useful if for example a maximum range span
3872 shall not be exceeded, or if the lower/upper range shouldn't go beyond certain
3873 values. For example, the following slot would limit the x axis to only
3874 positive ranges: \code if (newRange.lower < 0) plot->xAxis->setRange(0,
3875 newRange.size()); \endcode
3876 */
3877
3878 /*! \fn void QCPAxis::rangeChanged(const QCPRange &newRange, const QCPRange
3879 &oldRange) \overload
3880
3881 Additionally to the new range, this signal also provides the previous range
3882 held by the axis as \a oldRange.
3883 */
3884
3885 /*! \fn void QCPAxis::scaleTypeChanged(QCPAxis::ScaleType scaleType);
3886
3887 This signal is emitted when the scale type changes, by calls to \ref
3888 setScaleType
3889 */
3890
3891 /*! \fn void QCPAxis::selectionChanged(QCPAxis::SelectableParts selection)
3892
3893 This signal is emitted when the selection state of this axis has changed,
3894 either by user interaction or by a direct call to \ref setSelectedParts.
3895 */
3896
3897 /*! \fn void QCPAxis::selectableChanged(const QCPAxis::SelectableParts &parts);
3898
3899 This signal is emitted when the selectability changes, by calls to \ref
3900 setSelectableParts
3901 */
3902
3903 /* end of documentation of signals */
3904
3905 /*!
3906 Constructs an Axis instance of Type \a type for the axis rect \a parent.
3907
3908 Usually it isn't necessary to instantiate axes directly, because you can let
3909 QCustomPlot create them for you with \ref QCPAxisRect::addAxis. If you want to
3910 use own QCPAxis-subclasses however, create them manually and then inject them
3911 also via \ref QCPAxisRect::addAxis.
3912 */
3913 QCPAxis::QCPAxis(QCPAxisRect *parent, AxisType type)
3914 : QCPLayerable(parent->parentPlot(), QString(), parent),
3915 // axis base:
3916 mAxisType(type),
3917 mAxisRect(parent),
3918 mPadding(5),
3919 mOrientation(orientation(type)),
3920 mSelectableParts(spAxis | spTickLabels | spAxisLabel),
3921 mSelectedParts(spNone),
3922 mBasePen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
3923 mSelectedBasePen(QPen(Qt::blue, 2)),
3924 // axis label:
3925 mLabel(),
3926 mLabelFont(mParentPlot->font()),
3927 mSelectedLabelFont(
3928 QFont(mLabelFont.family(), mLabelFont.pointSize(), QFont::Bold)),
3929 mLabelColor(Qt::black),
3930 mSelectedLabelColor(Qt::blue),
3931 // tick labels:
3932 mTickLabels(true),
3933 mAutoTickLabels(true),
3934 mTickLabelType(ltNumber),
3935 mTickLabelFont(mParentPlot->font()),
3936 mSelectedTickLabelFont(QFont(mTickLabelFont.family(),
3937 mTickLabelFont.pointSize(), QFont::Bold)),
3938 mTickLabelColor(Qt::black),
3939 mSelectedTickLabelColor(Qt::blue),
3940 mDateTimeFormat(QLatin1String("hh:mm:ss\ndd.MM.yy")),
3941 mDateTimeSpec(Qt::LocalTime),
3942 mNumberPrecision(6),
3943 mNumberFormatChar('g'),
3944 mNumberBeautifulPowers(true),
3945 // ticks and subticks:
3946 mTicks(true),
3947 mTickStep(1),
3948 mSubTickCount(4),
3949 mAutoTickCount(6),
3950 mAutoTicks(true),
3951 mAutoTickStep(true),
3952 mAutoSubTicks(true),
3953 mTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
3954 mSelectedTickPen(QPen(Qt::blue, 2)),
3955 mSubTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
3956 mSelectedSubTickPen(QPen(Qt::blue, 2)),
3957 // scale and range:
3958 mRange(0, 5),
3959 mRangeReversed(false),
3960 mScaleType(stLinear),
3961 mScaleLogBase(10),
3962 mScaleLogBaseLogInv(1.0 / qLn(mScaleLogBase)),
3963 // internal members:
3964 mGrid(new QCPGrid(this)),
3965 mAxisPainter(new QCPAxisPainterPrivate(parent->parentPlot())),
3966 mLowestVisibleTick(0),
3967 mHighestVisibleTick(-1),
3968 mCachedMarginValid(false),
3969 mCachedMargin(0) {
3970 setParent(parent);
3971 mGrid->setVisible(false);
3972 setAntialiased(false);
3973 setLayer(
3974 mParentPlot->currentLayer()); // it's actually on that layer already, but
3975 // we want it in front of the grid, so we
3976 // place it on there again
3977
3978 if (type == atTop) {
3979 setTickLabelPadding(3);
3980 setLabelPadding(6);
3981 } else if (type == atRight) {
3982 setTickLabelPadding(7);
3983 setLabelPadding(12);
3984 } else if (type == atBottom) {
3985 setTickLabelPadding(3);
3986 setLabelPadding(3);
3987 } else if (type == atLeft) {
3988 setTickLabelPadding(5);
3989 setLabelPadding(10);
3990 }
3991 }
3992
3993 QCPAxis::~QCPAxis() {
3994 delete mAxisPainter;
3995 delete mGrid; // delete grid here instead of via parent ~QObject for better
3996 // defined deletion order
3997 }
3998
3999 /* No documentation as it is a property getter */
4000 int QCPAxis::tickLabelPadding() const { return mAxisPainter->tickLabelPadding; }
4001
4002 /* No documentation as it is a property getter */
4003 double QCPAxis::tickLabelRotation() const {
4004 return mAxisPainter->tickLabelRotation;
4005 }
4006
4007 /* No documentation as it is a property getter */
4008 QCPAxis::LabelSide QCPAxis::tickLabelSide() const {
4009 return mAxisPainter->tickLabelSide;
4010 }
4011
4012 /* No documentation as it is a property getter */
4013 QString QCPAxis::numberFormat() const {
4014 QString result;
4015 result.append(mNumberFormatChar);
4016 if (mNumberBeautifulPowers) {
4017 result.append(QLatin1Char('b'));
4018 if (mAxisPainter->numberMultiplyCross) result.append(QLatin1Char('c'));
4019 }
4020 return result;
4021 }
4022
4023 /* No documentation as it is a property getter */
4024 int QCPAxis::tickLengthIn() const { return mAxisPainter->tickLengthIn; }
4025
4026 /* No documentation as it is a property getter */
4027 int QCPAxis::tickLengthOut() const { return mAxisPainter->tickLengthOut; }
4028
4029 /* No documentation as it is a property getter */
4030 int QCPAxis::subTickLengthIn() const { return mAxisPainter->subTickLengthIn; }
4031
4032 /* No documentation as it is a property getter */
4033 int QCPAxis::subTickLengthOut() const { return mAxisPainter->subTickLengthOut; }
4034
4035 /* No documentation as it is a property getter */
4036 int QCPAxis::labelPadding() const { return mAxisPainter->labelPadding; }
4037
4038 /* No documentation as it is a property getter */
4039 int QCPAxis::offset() const { return mAxisPainter->offset; }
4040
4041 /* No documentation as it is a property getter */
4042 QCPLineEnding QCPAxis::lowerEnding() const { return mAxisPainter->lowerEnding; }
4043
4044 /* No documentation as it is a property getter */
4045 QCPLineEnding QCPAxis::upperEnding() const { return mAxisPainter->upperEnding; }
4046
4047 /*!
4048 Sets whether the axis uses a linear scale or a logarithmic scale. If \a type
4049 is set to \ref stLogarithmic, the logarithm base can be set with \ref
4050 setScaleLogBase. In logarithmic axis scaling, major tick marks appear at all
4051 powers of the logarithm base. Properties like tick step
4052 (\ref setTickStep) don't apply in logarithmic scaling. If you wish a decimal
4053 base but less major ticks, consider choosing a logarithm base of 100, 1000 or
4054 even higher.
4055
4056 If \a type is \ref stLogarithmic and the number format (\ref setNumberFormat)
4057 uses the 'b' option (beautifully typeset decimal powers), the display usually
4058 is "1 [multiplication sign] 10 [superscript] n", which looks unnatural for
4059 logarithmic scaling (the "1 [multiplication sign]" part). To only display the
4060 decimal power, set the number precision to zero with \ref setNumberPrecision.
4061 */
4062 void QCPAxis::setScaleType(QCPAxis::ScaleType type) {
4063 if (mScaleType != type) {
4064 mScaleType = type;
4065 if (mScaleType == stLogarithmic) setRange(mRange.sanitizedForLogScale());
4066 mCachedMarginValid = false;
4067 emit scaleTypeChanged(mScaleType);
4068 }
4069 }
4070
4071 /*!
4072 If \ref setScaleType is set to \ref stLogarithmic, \a base will be the
4073 logarithm base of the scaling. In logarithmic axis scaling, major tick marks
4074 appear at all powers of \a base.
4075
4076 Properties like tick step (\ref setTickStep) don't apply in logarithmic
4077 scaling. If you wish a decimal base but less major ticks, consider choosing \a
4078 base 100, 1000 or even higher.
4079 */
4080 void QCPAxis::setScaleLogBase(double base) {
4081 if (base > 1) {
4082 mScaleLogBase = base;
4083 mScaleLogBaseLogInv =
4084 1.0 / qLn(mScaleLogBase); // buffer for faster baseLog() calculation
4085 mCachedMarginValid = false;
4086 } else
4087 qDebug() << Q_FUNC_INFO
4088 << "Invalid logarithmic scale base (must be greater 1):" << base;
4089 }
4090
4091 /*!
4092 Sets the range of the axis.
4093
4094 This slot may be connected with the \ref rangeChanged signal of another axis
4095 so this axis is always synchronized with the other axis range, when it
4096 changes.
4097
4098 To invert the direction of an axis, use \ref setRangeReversed.
4099 */
4100 void QCPAxis::setRange(const QCPRange &range) {
4101 if (range.lower == mRange.lower && range.upper == mRange.upper) return;
4102
4103 if (!QCPRange::validRange(range)) return;
4104 QCPRange oldRange = mRange;
4105 if (mScaleType == stLogarithmic) {
4106 mRange = range.sanitizedForLogScale();
4107 } else {
4108 mRange = range.sanitizedForLinScale();
4109 }
4110 mCachedMarginValid = false;
4111 emit rangeChanged(mRange);
4112 emit rangeChanged(mRange, oldRange);
4113 }
4114
4115 /*!
4116 Sets whether the user can (de-)select the parts in \a selectable by clicking
4117 on the QCustomPlot surface. (When \ref QCustomPlot::setInteractions contains
4118 iSelectAxes.)
4119
4120 However, even when \a selectable is set to a value not allowing the selection
4121 of a specific part, it is still possible to set the selection of this part
4122 manually, by calling \ref setSelectedParts directly.
4123
4124 \see SelectablePart, setSelectedParts
4125 */
4126 void QCPAxis::setSelectableParts(const SelectableParts &selectable) {
4127 if (mSelectableParts != selectable) {
4128 mSelectableParts = selectable;
4129 emit selectableChanged(mSelectableParts);
4130 }
4131 }
4132
4133 /*!
4134 Sets the selected state of the respective axis parts described by \ref
4135 SelectablePart. When a part is selected, it uses a different pen/font.
4136
4137 The entire selection mechanism for axes is handled automatically when \ref
4138 QCustomPlot::setInteractions contains iSelectAxes. You only need to call this
4139 function when you wish to change the selection state manually.
4140
4141 This function can change the selection state of a part, independent of the
4142 \ref setSelectableParts setting.
4143
4144 emits the \ref selectionChanged signal when \a selected is different from the
4145 previous selection state.
4146
4147 \see SelectablePart, setSelectableParts, selectTest, setSelectedBasePen,
4148 setSelectedTickPen, setSelectedSubTickPen, setSelectedTickLabelFont,
4149 setSelectedLabelFont, setSelectedTickLabelColor, setSelectedLabelColor
4150 */
4151 void QCPAxis::setSelectedParts(const SelectableParts &selected) {
4152 if (mSelectedParts != selected) {
4153 mSelectedParts = selected;
4154 emit selectionChanged(mSelectedParts);
4155 }
4156 }
4157
4158 /*!
4159 \overload
4160
4161 Sets the lower and upper bound of the axis range.
4162
4163 To invert the direction of an axis, use \ref setRangeReversed.
4164
4165 There is also a slot to set a range, see \ref setRange(const QCPRange &range).
4166 */
4167 void QCPAxis::setRange(double lower, double upper) {
4168 if (lower == mRange.lower && upper == mRange.upper) return;
4169
4170 if (!QCPRange::validRange(lower, upper)) return;
4171 QCPRange oldRange = mRange;
4172 mRange.lower = lower;
4173 mRange.upper = upper;
4174 if (mScaleType == stLogarithmic) {
4175 mRange = mRange.sanitizedForLogScale();
4176 } else {
4177 mRange = mRange.sanitizedForLinScale();
4178 }
4179 mCachedMarginValid = false;
4180 emit rangeChanged(mRange);
4181 emit rangeChanged(mRange, oldRange);
4182 }
4183
4184 /*!
4185 \overload
4186
4187 Sets the range of the axis.
4188
4189 The \a position coordinate indicates together with the \a alignment parameter,
4190 where the new range will be positioned. \a size defines the size of the new
4191 axis range. \a alignment may be Qt::AlignLeft, Qt::AlignRight or
4192 Qt::AlignCenter. This will cause the left border, right border, or center of
4193 the range to be aligned with \a position. Any other values of \a alignment
4194 will default to Qt::AlignCenter.
4195 */
4196 void QCPAxis::setRange(double position, double size,
4197 Qt::AlignmentFlag alignment) {
4198 if (alignment == Qt::AlignLeft)
4199 setRange(position, position + size);
4200 else if (alignment == Qt::AlignRight)
4201 setRange(position - size, position);
4202 else // alignment == Qt::AlignCenter
4203 setRange(position - size / 2.0, position + size / 2.0);
4204 }
4205
4206 /*!
4207 Sets the lower bound of the axis range. The upper bound is not changed.
4208 \see setRange
4209 */
4210 void QCPAxis::setRangeLower(double lower) {
4211 if (mRange.lower == lower) return;
4212
4213 QCPRange oldRange = mRange;
4214 mRange.lower = lower;
4215 if (mScaleType == stLogarithmic) {
4216 mRange = mRange.sanitizedForLogScale();
4217 } else {
4218 mRange = mRange.sanitizedForLinScale();
4219 }
4220 mCachedMarginValid = false;
4221 emit rangeChanged(mRange);
4222 emit rangeChanged(mRange, oldRange);
4223 }
4224
4225 /*!
4226 Sets the upper bound of the axis range. The lower bound is not changed.
4227 \see setRange
4228 */
4229 void QCPAxis::setRangeUpper(double upper) {
4230 if (mRange.upper == upper) return;
4231
4232 QCPRange oldRange = mRange;
4233 mRange.upper = upper;
4234 if (mScaleType == stLogarithmic) {
4235 mRange = mRange.sanitizedForLogScale();
4236 } else {
4237 mRange = mRange.sanitizedForLinScale();
4238 }
4239 mCachedMarginValid = false;
4240 emit rangeChanged(mRange);
4241 emit rangeChanged(mRange, oldRange);
4242 }
4243
4244 /*!
4245 Sets whether the axis range (direction) is displayed reversed. Normally, the
4246 values on horizontal axes increase left to right, on vertical axes bottom to
4247 top. When \a reversed is set to true, the direction of increasing values is
4248 inverted.
4249
4250 Note that the range and data interface stays the same for reversed axes, e.g.
4251 the \a lower part of the \ref setRange interface will still reference the
4252 mathematically smaller number than the \a upper part.
4253 */
4254 void QCPAxis::setRangeReversed(bool reversed) {
4255 if (mRangeReversed != reversed) {
4256 mRangeReversed = reversed;
4257 mCachedMarginValid = false;
4258 }
4259 }
4260
4261 /*!
4262 Sets whether the tick positions should be calculated automatically (either
4263 from an automatically generated tick step or a tick step provided manually via
4264 \ref setTickStep, see \ref setAutoTickStep).
4265
4266 If \a on is set to false, you must provide the tick positions manually via
4267 \ref setTickVector. For these manual ticks you may let QCPAxis generate the
4268 appropriate labels automatically by leaving \ref setAutoTickLabels set to
4269 true. If you also wish to control the displayed labels manually, set \ref
4270 setAutoTickLabels to false and provide the label strings with \ref
4271 setTickVectorLabels.
4272
4273 If you need dynamically calculated tick vectors (and possibly tick label
4274 vectors), set the vectors in a slot connected to the \ref ticksRequest signal.
4275
4276 \see setAutoTickLabels, setAutoSubTicks, setAutoTickCount, setAutoTickStep
4277 */
4278 void QCPAxis::setAutoTicks(bool on) {
4279 if (mAutoTicks != on) {
4280 mAutoTicks = on;
4281 mCachedMarginValid = false;
4282 }
4283 }
4284
4285 /*!
4286 When \ref setAutoTickStep is true, \a approximateCount determines how many
4287 ticks should be generated in the visible range, approximately.
4288
4289 It's not guaranteed that this number of ticks is met exactly, but
4290 approximately within a tolerance of about two.
4291
4292 Only values greater than zero are accepted as \a approximateCount.
4293
4294 \see setAutoTickStep, setAutoTicks, setAutoSubTicks
4295 */
4296 void QCPAxis::setAutoTickCount(int approximateCount) {
4297 if (mAutoTickCount != approximateCount) {
4298 if (approximateCount > 0) {
4299 mAutoTickCount = approximateCount;
4300 mCachedMarginValid = false;
4301 } else
4302 qDebug() << Q_FUNC_INFO << "approximateCount must be greater than zero:"
4303 << approximateCount;
4304 }
4305 }
4306
4307 /*!
4308 Sets whether the tick labels are generated automatically. Depending on the
4309 tick label type (\ref ltNumber or \ref ltDateTime), the labels will either
4310 show the coordinate as floating point number (\ref setNumberFormat), or a
4311 date/time formatted according to \ref setDateTimeFormat.
4312
4313 If \a on is set to false, you should provide the tick labels via \ref
4314 setTickVectorLabels. This is usually used in a combination with \ref
4315 setAutoTicks set to false for complete control over tick positions and labels,
4316 e.g. when the ticks should be at multiples of pi and show "2pi", "3pi" etc. as
4317 tick labels.
4318
4319 If you need dynamically calculated tick vectors (and possibly tick label
4320 vectors), set the vectors in a slot connected to the \ref ticksRequest signal.
4321
4322 \see setAutoTicks
4323 */
4324 void QCPAxis::setAutoTickLabels(bool on) {
4325 if (mAutoTickLabels != on) {
4326 mAutoTickLabels = on;
4327 mCachedMarginValid = false;
4328 }
4329 }
4330
4331 /*!
4332 Sets whether the tick step, i.e. the interval between two (major) ticks, is
4333 calculated automatically. If \a on is set to true, the axis finds a tick step
4334 that is reasonable for human readable plots.
4335
4336 The number of ticks the algorithm aims for within the visible range can be
4337 specified with \ref setAutoTickCount.
4338
4339 If \a on is set to false, you may set the tick step manually with \ref
4340 setTickStep.
4341
4342 \see setAutoTicks, setAutoSubTicks, setAutoTickCount
4343 */
4344 void QCPAxis::setAutoTickStep(bool on) {
4345 if (mAutoTickStep != on) {
4346 mAutoTickStep = on;
4347 mCachedMarginValid = false;
4348 }
4349 }
4350
4351 /*!
4352 Sets whether the number of sub ticks in one tick interval is determined
4353 automatically. This works, as long as the tick step mantissa is a multiple of
4354 0.5. When \ref setAutoTickStep is enabled, this is always the case.
4355
4356 When \a on is set to false, you may set the sub tick count with \ref
4357 setSubTickCount manually.
4358
4359 \see setAutoTickCount, setAutoTicks, setAutoTickStep
4360 */
4361 void QCPAxis::setAutoSubTicks(bool on) {
4362 if (mAutoSubTicks != on) {
4363 mAutoSubTicks = on;
4364 mCachedMarginValid = false;
4365 }
4366 }
4367
4368 /*!
4369 Sets whether tick marks are displayed.
4370
4371 Note that setting \a show to false does not imply that tick labels are
4372 invisible, too. To achieve that, see \ref setTickLabels.
4373 */
4374 void QCPAxis::setTicks(bool show) {
4375 if (mTicks != show) {
4376 mTicks = show;
4377 mCachedMarginValid = false;
4378 }
4379 }
4380
4381 /*!
4382 Sets whether tick labels are displayed. Tick labels are the numbers drawn next
4383 to tick marks.
4384 */
4385 void QCPAxis::setTickLabels(bool show) {
4386 if (mTickLabels != show) {
4387 mTickLabels = show;
4388 mCachedMarginValid = false;
4389 }
4390 }
4391
4392 /*!
4393 Sets the distance between the axis base line (including any outward ticks) and
4394 the tick labels. \see setLabelPadding, setPadding
4395 */
4396 void QCPAxis::setTickLabelPadding(int padding) {
4397 if (mAxisPainter->tickLabelPadding != padding) {
4398 mAxisPainter->tickLabelPadding = padding;
4399 mCachedMarginValid = false;
4400 }
4401 }
4402
4403 /*!
4404 Sets whether the tick labels display numbers or dates/times.
4405
4406 If \a type is set to \ref ltNumber, the format specifications of \ref
4407 setNumberFormat apply.
4408
4409 If \a type is set to \ref ltDateTime, the format specifications of \ref
4410 setDateTimeFormat apply.
4411
4412 In QCustomPlot, date/time coordinates are <tt>double</tt> numbers representing
4413 the seconds since 1970-01-01T00:00:00 UTC. This format can be retrieved from
4414 QDateTime objects with the QDateTime::toTime_t() function. Since this only
4415 gives a resolution of one second, there is also the
4416 QDateTime::toMSecsSinceEpoch() function which returns the timespan described
4417 above in milliseconds. Divide its return value by 1000.0 to get a value with
4418 the format needed for date/time plotting, with a resolution of one
4419 millisecond.
4420
4421 Using the toMSecsSinceEpoch function allows dates that go back to 2nd January
4422 4713 B.C. (represented by a negative number), unlike the toTime_t function,
4423 which works with unsigned integers and thus only goes back to 1st January
4424 1970. So both for range and accuracy, use of toMSecsSinceEpoch()/1000.0 should
4425 be preferred as key coordinate for date/time axes.
4426
4427 \see setTickLabels
4428 */
4429 void QCPAxis::setTickLabelType(LabelType type) {
4430 if (mTickLabelType != type) {
4431 mTickLabelType = type;
4432 mCachedMarginValid = false;
4433 }
4434 }
4435
4436 /*!
4437 Sets the font of the tick labels.
4438
4439 \see setTickLabels, setTickLabelColor
4440 */
4441 void QCPAxis::setTickLabelFont(const QFont &font) {
4442 if (font != mTickLabelFont) {
4443 mTickLabelFont = font;
4444 mCachedMarginValid = false;
4445 }
4446 }
4447
4448 /*!
4449 Sets the color of the tick labels.
4450
4451 \see setTickLabels, setTickLabelFont
4452 */
4453 void QCPAxis::setTickLabelColor(const QColor &color) {
4454 if (color != mTickLabelColor) {
4455 mTickLabelColor = color;
4456 mCachedMarginValid = false;
4457 }
4458 }
4459
4460 /*!
4461 Sets the rotation of the tick labels. If \a degrees is zero, the labels are
4462 drawn normally. Else, the tick labels are drawn rotated by \a degrees
4463 clockwise. The specified angle is bound to values from -90 to 90 degrees.
4464
4465 If \a degrees is exactly -90, 0 or 90, the tick labels are centered on the
4466 tick coordinate. For other angles, the label is drawn with an offset such that
4467 it seems to point toward or away from the tick mark.
4468 */
4469 void QCPAxis::setTickLabelRotation(double degrees) {
4470 if (!qFuzzyIsNull(degrees - mAxisPainter->tickLabelRotation)) {
4471 mAxisPainter->tickLabelRotation = qBound(-90.0, degrees, 90.0);
4472 mCachedMarginValid = false;
4473 }
4474 }
4475
4476 /*!
4477 Sets whether the tick labels (numbers) shall appear inside or outside the axis
4478 rect.
4479
4480 The usual and default setting is \ref lsOutside. Very compact plots sometimes
4481 require tick labels to be inside the axis rect, to save space. If \a side is
4482 set to \ref lsInside, the tick labels appear on the inside are additionally
4483 clipped to the axis rect.
4484 */
4485 void QCPAxis::setTickLabelSide(LabelSide side) {
4486 mAxisPainter->tickLabelSide = side;
4487 mCachedMarginValid = false;
4488 }
4489
4490 /*!
4491 Sets the format in which dates and times are displayed as tick labels, if \ref
4492 setTickLabelType is \ref ltDateTime. for details about the \a format string,
4493 see the documentation of QDateTime::toString().
4494
4495 Newlines can be inserted with "\n".
4496
4497 \see setDateTimeSpec
4498 */
4499 void QCPAxis::setDateTimeFormat(const QString &format) {
4500 if (mDateTimeFormat != format) {
4501 mDateTimeFormat = format;
4502 mCachedMarginValid = false;
4503 }
4504 }
4505
4506 /*!
4507 Sets the time spec that is used for the date time values when \ref
4508 setTickLabelType is \ref ltDateTime.
4509
4510 The default value of QDateTime objects (and also QCustomPlot) is
4511 <tt>Qt::LocalTime</tt>. However, if the date time values passed to QCustomPlot
4512 are given in the UTC spec, set \a timeSpec to <tt>Qt::UTC</tt> to get the
4513 correct axis labels.
4514
4515 \see setDateTimeFormat
4516 */
4517 void QCPAxis::setDateTimeSpec(const Qt::TimeSpec &timeSpec) {
4518 mDateTimeSpec = timeSpec;
4519 }
4520
4521 /*!
4522 Sets the number format for the numbers drawn as tick labels (if tick label
4523 type is \ref ltNumber). This \a formatCode is an extended version of the
4524 format code used e.g. by QString::number() and QLocale::toString(). For
4525 reference about that, see the "Argument Formats" section in the detailed
4526 description of the QString class. \a formatCode is a string of one, two or
4527 three characters. The first character is identical to the normal format code
4528 used by Qt. In short, this means: 'e'/'E' scientific format, 'f' fixed format,
4529 'g'/'G' scientific or fixed, whichever is shorter.
4530
4531 The second and third characters are optional and specific to QCustomPlot:\n
4532 If the first char was 'e' or 'g', numbers are/might be displayed in the
4533 scientific format, e.g. "5.5e9", which is ugly in a plot. So when the second
4534 char of \a formatCode is set to 'b' (for "beautiful"), those exponential
4535 numbers are formatted in a more natural way, i.e. "5.5 [multiplication sign]
4536 10 [superscript] 9". By default, the multiplication sign is a centered dot. If
4537 instead a cross should be shown (as is usual in the USA), the third char of \a
4538 formatCode can be set to 'c'. The inserted multiplication signs are the UTF-8
4539 characters 215 (0xD7) for the cross and 183 (0xB7) for the dot.
4540
4541 If the scale type (\ref setScaleType) is \ref stLogarithmic and the \a
4542 formatCode uses the 'b' option (beautifully typeset decimal powers), the
4543 display usually is "1 [multiplication sign] 10 [superscript] n", which looks
4544 unnatural for logarithmic scaling (the "1 [multiplication sign]" part). To
4545 only display the decimal power, set the number precision to zero with \ref
4546 setNumberPrecision.
4547
4548 Examples for \a formatCode:
4549 \li \c g normal format code behaviour. If number is small, fixed format is
4550 used, if number is large, normal scientific format is used \li \c gb If number
4551 is small, fixed format is used, if number is large, scientific format is used
4552 with beautifully typeset decimal powers and a dot as multiplication sign \li
4553 \c ebc All numbers are in scientific format with beautifully typeset decimal
4554 power and a cross as multiplication sign \li \c fb illegal format code, since
4555 fixed format doesn't support (or need) beautifully typeset decimal powers.
4556 Format code will be reduced to 'f'. \li \c hello illegal format code, since
4557 first char is not 'e', 'E', 'f', 'g' or 'G'. Current format code will not be
4558 changed.
4559 */
4560 void QCPAxis::setNumberFormat(const QString &formatCode) {
4561 if (formatCode.isEmpty()) {
4562 qDebug() << Q_FUNC_INFO << "Passed formatCode is empty";
4563 return;
4564 }
4565 mCachedMarginValid = false;
4566
4567 // interpret first char as number format char:
4568 QString allowedFormatChars(QLatin1String("eEfgG"));
4569 if (allowedFormatChars.contains(formatCode.at(0))) {
4570 mNumberFormatChar = QLatin1Char(formatCode.at(0).toLatin1());
4571 } else {
4572 qDebug() << Q_FUNC_INFO
4573 << "Invalid number format code (first char not in 'eEfgG'):"
4574 << formatCode;
4575 return;
4576 }
4577 if (formatCode.length() < 2) {
4578 mNumberBeautifulPowers = false;
4579 mAxisPainter->numberMultiplyCross = false;
4580 return;
4581 }
4582
4583 // interpret second char as indicator for beautiful decimal powers:
4584 if (formatCode.at(1) == QLatin1Char('b') &&
4585 (mNumberFormatChar == QLatin1Char('e') ||
4586 mNumberFormatChar == QLatin1Char('g'))) {
4587 mNumberBeautifulPowers = true;
4588 } else {
4589 qDebug() << Q_FUNC_INFO
4590 << "Invalid number format code (second char not 'b' or first char "
4591 "neither 'e' nor 'g'):"
4592 << formatCode;
4593 return;
4594 }
4595 if (formatCode.length() < 3) {
4596 mAxisPainter->numberMultiplyCross = false;
4597 return;
4598 }
4599
4600 // interpret third char as indicator for dot or cross multiplication symbol:
4601 if (formatCode.at(2) == QLatin1Char('c')) {
4602 mAxisPainter->numberMultiplyCross = true;
4603 } else if (formatCode.at(2) == QLatin1Char('d')) {
4604 mAxisPainter->numberMultiplyCross = false;
4605 } else {
4606 qDebug() << Q_FUNC_INFO
4607 << "Invalid number format code (third char neither 'c' nor 'd'):"
4608 << formatCode;
4609 return;
4610 }
4611 }
4612
4613 /*!
4614 Sets the precision of the tick label numbers. See QLocale::toString(double i,
4615 char f, int prec) for details. The effect of precisions are most notably for
4616 number Formats starting with 'e', see \ref setNumberFormat
4617
4618 If the scale type (\ref setScaleType) is \ref stLogarithmic and the number
4619 format (\ref setNumberFormat) uses the 'b' format code (beautifully typeset
4620 decimal powers), the display usually is "1 [multiplication sign] 10
4621 [superscript] n", which looks unnatural for logarithmic scaling (the redundant
4622 "1 [multiplication sign]" part). To only display the decimal power "10
4623 [superscript] n", set \a precision to zero.
4624 */
4625 void QCPAxis::setNumberPrecision(int precision) {
4626 if (mNumberPrecision != precision) {
4627 mNumberPrecision = precision;
4628 mCachedMarginValid = false;
4629 }
4630 }
4631
4632 /*!
4633 If \ref setAutoTickStep is set to false, use this function to set the tick
4634 step manually. The tick step is the interval between (major) ticks, in plot
4635 coordinates. \see setSubTickCount
4636 */
4637 void QCPAxis::setTickStep(double step) {
4638 if (mTickStep != step) {
4639 mTickStep = step;
4640 mCachedMarginValid = false;
4641 }
4642 }
4643
4644 /*!
4645 If you want full control over what ticks (and possibly labels) the axes show,
4646 this function is used to set the coordinates at which ticks will appear.\ref
4647 setAutoTicks must be disabled, else the provided tick vector will be
4648 overwritten with automatically generated tick coordinates upon replot. The
4649 labels of the ticks can be generated automatically when \ref setAutoTickLabels
4650 is left enabled. If it is disabled, you can set the labels manually with \ref
4651 setTickVectorLabels.
4652
4653 \a vec is a vector containing the positions of the ticks, in plot coordinates.
4654
4655 \warning \a vec must be sorted in ascending order, no additional checks are
4656 made to ensure this.
4657
4658 \see setTickVectorLabels
4659 */
4660 void QCPAxis::setTickVector(const QVector<double> &vec) {
4661 // don't check whether mTickVector != vec here, because it takes longer than
4662 // we would save
4663 mTickVector = vec;
4664 mCachedMarginValid = false;
4665 }
4666
4667 /*!
4668 If you want full control over what ticks and labels the axes show, this
4669 function is used to set a number of QStrings that will be displayed at the
4670 tick positions which you need to provide with \ref setTickVector. These two
4671 vectors should have the same size. (Note that you need to disable \ref
4672 setAutoTicks and \ref setAutoTickLabels first.)
4673
4674 \a vec is a vector containing the labels of the ticks. The entries correspond
4675 to the respective indices in the tick vector, passed via \ref setTickVector.
4676
4677 \see setTickVector
4678 */
4679 void QCPAxis::setTickVectorLabels(const QVector<QString> &vec) {
4680 // don't check whether mTickVectorLabels != vec here, because it takes longer
4681 // than we would save
4682 mTickVectorLabels = vec;
4683 mCachedMarginValid = false;
4684 }
4685
4686 /*!
4687 Sets the length of the ticks in pixels. \a inside is the length the ticks will
4688 reach inside the plot and \a outside is the length they will reach outside the
4689 plot. If \a outside is greater than zero, the tick labels and axis label will
4690 increase their distance to the axis accordingly, so they won't collide with
4691 the ticks.
4692
4693 \see setSubTickLength, setTickLengthIn, setTickLengthOut
4694 */
4695 void QCPAxis::setTickLength(int inside, int outside) {
4696 setTickLengthIn(inside);
4697 setTickLengthOut(outside);
4698 }
4699
4700 /*!
4701 Sets the length of the inward ticks in pixels. \a inside is the length the
4702 ticks will reach inside the plot.
4703
4704 \see setTickLengthOut, setTickLength, setSubTickLength
4705 */
4706 void QCPAxis::setTickLengthIn(int inside) {
4707 if (mAxisPainter->tickLengthIn != inside) {
4708 mAxisPainter->tickLengthIn = inside;
4709 }
4710 }
4711
4712 /*!
4713 Sets the length of the outward ticks in pixels. \a outside is the length the
4714 ticks will reach outside the plot. If \a outside is greater than zero, the
4715 tick labels and axis label will increase their distance to the axis
4716 accordingly, so they won't collide with the ticks.
4717
4718 \see setTickLengthIn, setTickLength, setSubTickLength
4719 */
4720 void QCPAxis::setTickLengthOut(int outside) {
4721 if (mAxisPainter->tickLengthOut != outside) {
4722 mAxisPainter->tickLengthOut = outside;
4723 mCachedMarginValid = false; // only outside tick length can change margin
4724 }
4725 }
4726
4727 /*!
4728 Sets the number of sub ticks in one (major) tick step. A sub tick count of
4729 three for example, divides the tick intervals in four sub intervals.
4730
4731 By default, the number of sub ticks is chosen automatically in a reasonable
4732 manner as long as the mantissa of the tick step is a multiple of 0.5. When
4733 \ref setAutoTickStep is enabled, this is always the case.
4734
4735 If you want to disable automatic sub tick count and use this function to set
4736 the count manually, see \ref setAutoSubTicks.
4737 */
4738 void QCPAxis::setSubTickCount(int count) { mSubTickCount = count; }
4739
4740 /*!
4741 Sets the length of the subticks in pixels. \a inside is the length the
4742 subticks will reach inside the plot and \a outside is the length they will
4743 reach outside the plot. If \a outside is greater than zero, the tick labels
4744 and axis label will increase their distance to the axis accordingly, so they
4745 won't collide with the ticks.
4746
4747 \see setTickLength, setSubTickLengthIn, setSubTickLengthOut
4748 */
4749 void QCPAxis::setSubTickLength(int inside, int outside) {
4750 setSubTickLengthIn(inside);
4751 setSubTickLengthOut(outside);
4752 }
4753
4754 /*!
4755 Sets the length of the inward subticks in pixels. \a inside is the length the
4756 subticks will reach inside the plot.
4757
4758 \see setSubTickLengthOut, setSubTickLength, setTickLength
4759 */
4760 void QCPAxis::setSubTickLengthIn(int inside) {
4761 if (mAxisPainter->subTickLengthIn != inside) {
4762 mAxisPainter->subTickLengthIn = inside;
4763 }
4764 }
4765
4766 /*!
4767 Sets the length of the outward subticks in pixels. \a outside is the length
4768 the subticks will reach outside the plot. If \a outside is greater than zero,
4769 the tick labels will increase their distance to the axis accordingly, so they
4770 won't collide with the ticks.
4771
4772 \see setSubTickLengthIn, setSubTickLength, setTickLength
4773 */
4774 void QCPAxis::setSubTickLengthOut(int outside) {
4775 if (mAxisPainter->subTickLengthOut != outside) {
4776 mAxisPainter->subTickLengthOut = outside;
4777 mCachedMarginValid = false; // only outside tick length can change margin
4778 }
4779 }
4780
4781 /*!
4782 Sets the pen, the axis base line is drawn with.
4783
4784 \see setTickPen, setSubTickPen
4785 */
4786 void QCPAxis::setBasePen(const QPen &pen) { mBasePen = pen; }
4787
4788 /*!
4789 Sets the pen, tick marks will be drawn with.
4790
4791 \see setTickLength, setBasePen
4792 */
4793 void QCPAxis::setTickPen(const QPen &pen) { mTickPen = pen; }
4794
4795 /*!
4796 Sets the pen, subtick marks will be drawn with.
4797
4798 \see setSubTickCount, setSubTickLength, setBasePen
4799 */
4800 void QCPAxis::setSubTickPen(const QPen &pen) { mSubTickPen = pen; }
4801
4802 /*!
4803 Sets the font of the axis label.
4804
4805 \see setLabelColor
4806 */
4807 void QCPAxis::setLabelFont(const QFont &font) {
4808 if (mLabelFont != font) {
4809 mLabelFont = font;
4810 mCachedMarginValid = false;
4811 }
4812 }
4813
4814 /*!
4815 Sets the color of the axis label.
4816
4817 \see setLabelFont
4818 */
4819 void QCPAxis::setLabelColor(const QColor &color) { mLabelColor = color; }
4820
4821 /*!
4822 Sets the text of the axis label that will be shown below/above or next to the
4823 axis, depending on its orientation. To disable axis labels, pass an empty
4824 string as \a str.
4825 */
4826 void QCPAxis::setLabel(const QString &str) {
4827 if (mLabel != str) {
4828 mLabel = str;
4829 mCachedMarginValid = false;
4830 }
4831 }
4832
4833 /*!
4834 Sets the distance between the tick labels and the axis label.
4835
4836 \see setTickLabelPadding, setPadding
4837 */
4838 void QCPAxis::setLabelPadding(int padding) {
4839 if (mAxisPainter->labelPadding != padding) {
4840 mAxisPainter->labelPadding = padding;
4841 mCachedMarginValid = false;
4842 }
4843 }
4844
4845 /*!
4846 Sets the padding of the axis.
4847
4848 When \ref QCPAxisRect::setAutoMargins is enabled, the padding is the
4849 additional outer most space, that is left blank.
4850
4851 The axis padding has no meaning if \ref QCPAxisRect::setAutoMargins is
4852 disabled.
4853
4854 \see setLabelPadding, setTickLabelPadding
4855 */
4856 void QCPAxis::setPadding(int padding) {
4857 if (mPadding != padding) {
4858 mPadding = padding;
4859 mCachedMarginValid = false;
4860 }
4861 }
4862
4863 /*!
4864 Sets the offset the axis has to its axis rect side.
4865
4866 If an axis rect side has multiple axes and automatic margin calculation is
4867 enabled for that side, only the offset of the inner most axis has meaning
4868 (even if it is set to be invisible). The offset of the other, outer axes is
4869 controlled automatically, to place them at appropriate positions.
4870 */
4871 void QCPAxis::setOffset(int offset) { mAxisPainter->offset = offset; }
4872
4873 /*!
4874 Sets the font that is used for tick labels when they are selected.
4875
4876 \see setTickLabelFont, setSelectableParts, setSelectedParts,
4877 QCustomPlot::setInteractions
4878 */
4879 void QCPAxis::setSelectedTickLabelFont(const QFont &font) {
4880 if (font != mSelectedTickLabelFont) {
4881 mSelectedTickLabelFont = font;
4882 // don't set mCachedMarginValid to false here because margin calculation is
4883 // always done with non-selected fonts
4884 }
4885 }
4886
4887 /*!
4888 Sets the font that is used for the axis label when it is selected.
4889
4890 \see setLabelFont, setSelectableParts, setSelectedParts,
4891 QCustomPlot::setInteractions
4892 */
4893 void QCPAxis::setSelectedLabelFont(const QFont &font) {
4894 mSelectedLabelFont = font;
4895 // don't set mCachedMarginValid to false here because margin calculation is
4896 // always done with non-selected fonts
4897 }
4898
4899 /*!
4900 Sets the color that is used for tick labels when they are selected.
4901
4902 \see setTickLabelColor, setSelectableParts, setSelectedParts,
4903 QCustomPlot::setInteractions
4904 */
4905 void QCPAxis::setSelectedTickLabelColor(const QColor &color) {
4906 if (color != mSelectedTickLabelColor) {
4907 mSelectedTickLabelColor = color;
4908 }
4909 }
4910
4911 /*!
4912 Sets the color that is used for the axis label when it is selected.
4913
4914 \see setLabelColor, setSelectableParts, setSelectedParts,
4915 QCustomPlot::setInteractions
4916 */
4917 void QCPAxis::setSelectedLabelColor(const QColor &color) {
4918 mSelectedLabelColor = color;
4919 }
4920
4921 /*!
4922 Sets the pen that is used to draw the axis base line when selected.
4923
4924 \see setBasePen, setSelectableParts, setSelectedParts,
4925 QCustomPlot::setInteractions
4926 */
4927 void QCPAxis::setSelectedBasePen(const QPen &pen) { mSelectedBasePen = pen; }
4928
4929 /*!
4930 Sets the pen that is used to draw the (major) ticks when selected.
4931
4932 \see setTickPen, setSelectableParts, setSelectedParts,
4933 QCustomPlot::setInteractions
4934 */
4935 void QCPAxis::setSelectedTickPen(const QPen &pen) { mSelectedTickPen = pen; }
4936
4937 /*!
4938 Sets the pen that is used to draw the subticks when selected.
4939
4940 \see setSubTickPen, setSelectableParts, setSelectedParts,
4941 QCustomPlot::setInteractions
4942 */
4943 void QCPAxis::setSelectedSubTickPen(const QPen &pen) {
4944 mSelectedSubTickPen = pen;
4945 }
4946
4947 /*!
4948 Sets the style for the lower axis ending. See the documentation of
4949 QCPLineEnding for available styles.
4950
4951 For horizontal axes, this method refers to the left ending, for vertical axes
4952 the bottom ending. Note that this meaning does not change when the axis range
4953 is reversed with \ref setRangeReversed.
4954
4955 \see setUpperEnding
4956 */
4957 void QCPAxis::setLowerEnding(const QCPLineEnding &ending) {
4958 mAxisPainter->lowerEnding = ending;
4959 }
4960
4961 /*!
4962 Sets the style for the upper axis ending. See the documentation of
4963 QCPLineEnding for available styles.
4964
4965 For horizontal axes, this method refers to the right ending, for vertical axes
4966 the top ending. Note that this meaning does not change when the axis range is
4967 reversed with \ref setRangeReversed.
4968
4969 \see setLowerEnding
4970 */
4971 void QCPAxis::setUpperEnding(const QCPLineEnding &ending) {
4972 mAxisPainter->upperEnding = ending;
4973 }
4974
4975 /*!
4976 If the scale type (\ref setScaleType) is \ref stLinear, \a diff is added to
4977 the lower and upper bounds of the range. The range is simply moved by \a diff.
4978
4979 If the scale type is \ref stLogarithmic, the range bounds are multiplied by \a
4980 diff. This corresponds to an apparent "linear" move in logarithmic scaling by
4981 a distance of log(diff).
4982 */
4983 void QCPAxis::moveRange(double diff) {
4984 QCPRange oldRange = mRange;
4985 if (mScaleType == stLinear) {
4986 mRange.lower += diff;
4987 mRange.upper += diff;
4988 } else // mScaleType == stLogarithmic
4989 {
4990 mRange.lower *= diff;
4991 mRange.upper *= diff;
4992 }
4993 mCachedMarginValid = false;
4994 emit rangeChanged(mRange);
4995 emit rangeChanged(mRange, oldRange);
4996 }
4997
4998 /*!
4999 Scales the range of this axis by \a factor around the coordinate \a center.
5000 For example, if \a factor is 2.0, \a center is 1.0, then the axis range will
5001 double its size, and the point at coordinate 1.0 won't have changed its
5002 position in the QCustomPlot widget (i.e. coordinates around 1.0 will have
5003 moved symmetrically closer to 1.0).
5004 */
5005 void QCPAxis::scaleRange(double factor, double center) {
5006 QCPRange oldRange = mRange;
5007 if (mScaleType == stLinear) {
5008 QCPRange newRange;
5009 newRange.lower = (mRange.lower - center) * factor + center;
5010 newRange.upper = (mRange.upper - center) * factor + center;
5011 if (QCPRange::validRange(newRange))
5012 mRange = newRange.sanitizedForLinScale();
5013 } else // mScaleType == stLogarithmic
5014 {
5015 if ((mRange.upper < 0 && center < 0) ||
5016 (mRange.upper > 0 &&
5017 center > 0)) // make sure center has same sign as range
5018 {
5019 QCPRange newRange;
5020 newRange.lower = qPow(mRange.lower / center, factor) * center;
5021 newRange.upper = qPow(mRange.upper / center, factor) * center;
5022 if (QCPRange::validRange(newRange))
5023 mRange = newRange.sanitizedForLogScale();
5024 } else
5025 qDebug() << Q_FUNC_INFO
5026 << "Center of scaling operation doesn't lie in same logarithmic "
5027 "sign domain as range:"
5028 << center;
5029 }
5030 mCachedMarginValid = false;
5031 emit rangeChanged(mRange);
5032 emit rangeChanged(mRange, oldRange);
5033 }
5034
5035 /*!
5036 Scales the range of this axis to have a certain scale \a ratio to \a
5037 otherAxis. The scaling will be done around the center of the current axis
5038 range.
5039
5040 For example, if \a ratio is 1, this axis is the \a yAxis and \a otherAxis is
5041 \a xAxis, graphs plotted with those axes will appear in a 1:1 aspect ratio,
5042 independent of the aspect ratio the axis rect has.
5043
5044 This is an operation that changes the range of this axis once, it doesn't fix
5045 the scale ratio indefinitely. Note that calling this function in the
5046 constructor of the QCustomPlot's parent won't have the desired effect, since
5047 the widget dimensions aren't defined yet, and a resizeEvent will follow.
5048 */
5049 void QCPAxis::setScaleRatio(const QCPAxis *otherAxis, double ratio) {
5050 int otherPixelSize, ownPixelSize;
5051
5052 if (otherAxis->orientation() == Qt::Horizontal)
5053 otherPixelSize = otherAxis->axisRect()->width();
5054 else
5055 otherPixelSize = otherAxis->axisRect()->height();
5056
5057 if (orientation() == Qt::Horizontal)
5058 ownPixelSize = axisRect()->width();
5059 else
5060 ownPixelSize = axisRect()->height();
5061
5062 double newRangeSize =
5063 ratio * otherAxis->range().size() * ownPixelSize / (double)otherPixelSize;
5064 setRange(range().center(), newRangeSize, Qt::AlignCenter);
5065 }
5066
5067 /*!
5068 Changes the axis range such that all plottables associated with this axis are
5069 fully visible in that dimension.
5070
5071 \see QCPAbstractPlottable::rescaleAxes, QCustomPlot::rescaleAxes
5072 */
5073 void QCPAxis::rescale(bool onlyVisiblePlottables) {
5074 QList<QCPAbstractPlottable *> p = plottables();
5075 QCPRange newRange;
5076 bool haveRange = false;
5077 for (int i = 0; i < p.size(); ++i) {
5078 if (!p.at(i)->realVisibility() && onlyVisiblePlottables) continue;
5079 QCPRange plottableRange;
5080 bool currentFoundRange;
5081 QCPAbstractPlottable::SignDomain signDomain = QCPAbstractPlottable::sdBoth;
5082 if (mScaleType == stLogarithmic)
5083 signDomain = (mRange.upper < 0 ? QCPAbstractPlottable::sdNegative
5084 : QCPAbstractPlottable::sdPositive);
5085 if (p.at(i)->keyAxis() == this)
5086 plottableRange = p.at(i)->getKeyRange(currentFoundRange, signDomain);
5087 else
5088 plottableRange = p.at(i)->getValueRange(currentFoundRange, signDomain);
5089 if (currentFoundRange) {
5090 if (!haveRange)
5091 newRange = plottableRange;
5092 else
5093 newRange.expand(plottableRange);
5094 haveRange = true;
5095 }
5096 }
5097 if (haveRange) {
5098 if (!QCPRange::validRange(
5099 newRange)) // likely due to range being zero (plottable has only
5100 // constant data in this axis dimension), shift current
5101 // range to at least center the plottable
5102 {
5103 double center =
5104 (newRange.lower + newRange.upper) *
5105 0.5; // upper and lower should be equal anyway, but just to make
5106 // sure, incase validRange returned false for other reason
5107 if (mScaleType == stLinear) {
5108 newRange.lower = center - mRange.size() / 2.0;
5109 newRange.upper = center + mRange.size() / 2.0;
5110 } else // mScaleType == stLogarithmic
5111 {
5112 newRange.lower = center / qSqrt(mRange.upper / mRange.lower);
5113 newRange.upper = center * qSqrt(mRange.upper / mRange.lower);
5114 }
5115 }
5116 setRange(newRange);
5117 }
5118 }
5119
5120 /*!
5121 Transforms \a value, in pixel coordinates of the QCustomPlot widget, to axis
5122 coordinates.
5123 */
5124 double QCPAxis::pixelToCoord(double value) const {
5125 if (orientation() == Qt::Horizontal) {
5126 if (mScaleType == stLinear) {
5127 if (!mRangeReversed)
5128 return (value - mAxisRect->left()) / (double)mAxisRect->width() *
5129 mRange.size() +
5130 mRange.lower;
5131 else
5132 return -(value - mAxisRect->left()) / (double)mAxisRect->width() *
5133 mRange.size() +
5134 mRange.upper;
5135 } else // mScaleType == stLogarithmic
5136 {
5137 if (!mRangeReversed)
5138 return qPow(mRange.upper / mRange.lower,
5139 (value - mAxisRect->left()) / (double)mAxisRect->width()) *
5140 mRange.lower;
5141 else
5142 return qPow(mRange.upper / mRange.lower,
5143 (mAxisRect->left() - value) / (double)mAxisRect->width()) *
5144 mRange.upper;
5145 }
5146 } else // orientation() == Qt::Vertical
5147 {
5148 if (mScaleType == stLinear) {
5149 if (!mRangeReversed)
5150 return (mAxisRect->bottom() - value) / (double)mAxisRect->height() *
5151 mRange.size() +
5152 mRange.lower;
5153 else
5154 return -(mAxisRect->bottom() - value) / (double)mAxisRect->height() *
5155 mRange.size() +
5156 mRange.upper;
5157 } else // mScaleType == stLogarithmic
5158 {
5159 if (!mRangeReversed)
5160 return qPow(mRange.upper / mRange.lower,
5161 (mAxisRect->bottom() - value) /
5162 (double)mAxisRect->height()) *
5163 mRange.lower;
5164 else
5165 return qPow(mRange.upper / mRange.lower,
5166 (value - mAxisRect->bottom()) /
5167 (double)mAxisRect->height()) *
5168 mRange.upper;
5169 }
5170 }
5171 }
5172
5173 /*!
5174 Transforms \a value, in coordinates of the axis, to pixel coordinates of the
5175 QCustomPlot widget.
5176 */
5177 double QCPAxis::coordToPixel(double value) const {
5178 if (orientation() == Qt::Horizontal) {
5179 if (mScaleType == stLinear) {
5180 if (!mRangeReversed)
5181 return (value - mRange.lower) / mRange.size() * mAxisRect->width() +
5182 mAxisRect->left();
5183 else
5184 return (mRange.upper - value) / mRange.size() * mAxisRect->width() +
5185 mAxisRect->left();
5186 } else // mScaleType == stLogarithmic
5187 {
5188 if (value >= 0 &&
5189 mRange.upper < 0) // invalid value for logarithmic scale, just draw
5190 // it outside visible range
5191 return !mRangeReversed ? mAxisRect->right() + 200
5192 : mAxisRect->left() - 200;
5193 else if (value <= 0 &&
5194 mRange.upper > 0) // invalid value for logarithmic scale, just
5195 // draw it outside visible range
5196 return !mRangeReversed ? mAxisRect->left() - 200
5197 : mAxisRect->right() + 200;
5198 else {
5199 if (!mRangeReversed)
5200 return baseLog(value / mRange.lower) /
5201 baseLog(mRange.upper / mRange.lower) * mAxisRect->width() +
5202 mAxisRect->left();
5203 else
5204 return baseLog(mRange.upper / value) /
5205 baseLog(mRange.upper / mRange.lower) * mAxisRect->width() +
5206 mAxisRect->left();
5207 }
5208 }
5209 } else // orientation() == Qt::Vertical
5210 {
5211 if (mScaleType == stLinear) {
5212 if (!mRangeReversed)
5213 return mAxisRect->bottom() -
5214 (value - mRange.lower) / mRange.size() * mAxisRect->height();
5215 else
5216 return mAxisRect->bottom() -
5217 (mRange.upper - value) / mRange.size() * mAxisRect->height();
5218 } else // mScaleType == stLogarithmic
5219 {
5220 if (value >= 0 &&
5221 mRange.upper < 0) // invalid value for logarithmic scale, just draw
5222 // it outside visible range
5223 return !mRangeReversed ? mAxisRect->top() - 200
5224 : mAxisRect->bottom() + 200;
5225 else if (value <= 0 &&
5226 mRange.upper > 0) // invalid value for logarithmic scale, just
5227 // draw it outside visible range
5228 return !mRangeReversed ? mAxisRect->bottom() + 200
5229 : mAxisRect->top() - 200;
5230 else {
5231 if (!mRangeReversed)
5232 return mAxisRect->bottom() -
5233 baseLog(value / mRange.lower) /
5234 baseLog(mRange.upper / mRange.lower) * mAxisRect->height();
5235 else
5236 return mAxisRect->bottom() -
5237 baseLog(mRange.upper / value) /
5238 baseLog(mRange.upper / mRange.lower) * mAxisRect->height();
5239 }
5240 }
5241 }
5242 }
5243
5244 /*!
5245 Returns the part of the axis that is hit by \a pos (in pixels). The return
5246 value of this function is independent of the user-selectable parts defined
5247 with \ref setSelectableParts. Further, this function does not change the
5248 current selection state of the axis.
5249
5250 If the axis is not visible (\ref setVisible), this function always returns
5251 \ref spNone.
5252
5253 \see setSelectedParts, setSelectableParts, QCustomPlot::setInteractions
5254 */
5255 QCPAxis::SelectablePart QCPAxis::getPartAt(const QPointF &pos) const {
5256 if (!mVisible) return spNone;
5257
5258 if (mAxisPainter->axisSelectionBox().contains(pos.toPoint()))
5259 return spAxis;
5260 else if (mAxisPainter->tickLabelsSelectionBox().contains(pos.toPoint()))
5261 return spTickLabels;
5262 else if (mAxisPainter->labelSelectionBox().contains(pos.toPoint()))
5263 return spAxisLabel;
5264 else
5265 return spNone;
5266 }
5267
5268 /* inherits documentation from base class */
5269 double QCPAxis::selectTest(const QPointF &pos, bool onlySelectable,
5270 QVariant *details) const {
5271 if (!mParentPlot) return -1;
5272 SelectablePart part = getPartAt(pos);
5273 if ((onlySelectable && !mSelectableParts.testFlag(part)) || part == spNone)
5274 return -1;
5275
5276 if (details) details->setValue(part);
5277 return mParentPlot->selectionTolerance() * 0.99;
5278 }
5279
5280 /*!
5281 Returns a list of all the plottables that have this axis as key or value axis.
5282
5283 If you are only interested in plottables of type QCPGraph, see \ref graphs.
5284
5285 \see graphs, items
5286 */
5287 QList<QCPAbstractPlottable *> QCPAxis::plottables() const {
5288 QList<QCPAbstractPlottable *> result;
5289 if (!mParentPlot) return result;
5290
5291 for (int i = 0; i < mParentPlot->mPlottables.size(); ++i) {
5292 if (mParentPlot->mPlottables.at(i)->keyAxis() == this ||
5293 mParentPlot->mPlottables.at(i)->valueAxis() == this)
5294 result.append(mParentPlot->mPlottables.at(i));
5295 }
5296 return result;
5297 }
5298
5299 /*!
5300 Returns a list of all the graphs that have this axis as key or value axis.
5301
5302 \see plottables, items
5303 */
5304 QList<QCPGraph *> QCPAxis::graphs() const {
5305 QList<QCPGraph *> result;
5306 if (!mParentPlot) return result;
5307
5308 for (int i = 0; i < mParentPlot->mGraphs.size(); ++i) {
5309 if (mParentPlot->mGraphs.at(i)->keyAxis() == this ||
5310 mParentPlot->mGraphs.at(i)->valueAxis() == this)
5311 result.append(mParentPlot->mGraphs.at(i));
5312 }
5313 return result;
5314 }
5315
5316 /*!
5317 Returns a list of all the items that are associated with this axis. An item is
5318 considered associated with an axis if at least one of its positions uses the
5319 axis as key or value axis.
5320
5321 \see plottables, graphs
5322 */
5323 QList<QCPAbstractItem *> QCPAxis::items() const {
5324 QList<QCPAbstractItem *> result;
5325 if (!mParentPlot) return result;
5326
5327 for (int itemId = 0; itemId < mParentPlot->mItems.size(); ++itemId) {
5328 QList<QCPItemPosition *> positions =
5329 mParentPlot->mItems.at(itemId)->positions();
5330 for (int posId = 0; posId < positions.size(); ++posId) {
5331 if (positions.at(posId)->keyAxis() == this ||
5332 positions.at(posId)->valueAxis() == this) {
5333 result.append(mParentPlot->mItems.at(itemId));
5334 break;
5335 }
5336 }
5337 }
5338 return result;
5339 }
5340
5341 /*!
5342 Transforms a margin side to the logically corresponding axis type.
5343 (QCP::msLeft to QCPAxis::atLeft, QCP::msRight to QCPAxis::atRight, etc.)
5344 */
5345 QCPAxis::AxisType QCPAxis::marginSideToAxisType(QCP::MarginSide side) {
5346 switch (side) {
5347 case QCP::msLeft:
5348 return atLeft;
5349 case QCP::msRight:
5350 return atRight;
5351 case QCP::msTop:
5352 return atTop;
5353 case QCP::msBottom:
5354 return atBottom;
5355 default:
5356 break;
5357 }
5358 qDebug() << Q_FUNC_INFO << "Invalid margin side passed:" << (int)side;
5359 return atLeft;
5360 }
5361
5362 /*!
5363 Returns the axis type that describes the opposite axis of an axis with the
5364 specified \a type.
5365 */
5366 QCPAxis::AxisType QCPAxis::opposite(QCPAxis::AxisType type) {
5367 switch (type) {
5368 case atLeft:
5369 return atRight;
5370 break;
5371 case atRight:
5372 return atLeft;
5373 break;
5374 case atBottom:
5375 return atTop;
5376 break;
5377 case atTop:
5378 return atBottom;
5379 break;
5380 default:
5381 qDebug() << Q_FUNC_INFO << "invalid axis type";
5382 return atLeft;
5383 break;
5384 }
5385 }
5386
5387 /*! \internal
5388
5389 This function is called to prepare the tick vector, sub tick vector and tick
5390 label vector. If \ref setAutoTicks is set to true, appropriate tick values are
5391 determined automatically via \ref generateAutoTicks. If it's set to false, the
5392 signal ticksRequest is emitted, which can be used to provide external tick
5393 positions. Then the sub tick vectors and tick label vectors are created.
5394 */
5395 void QCPAxis::setupTickVectors() {
5396 if (!mParentPlot) return;
5397 if ((!mTicks && !mTickLabels && !mGrid->visible()) || mRange.size() <= 0)
5398 return;
5399
5400 // fill tick vectors, either by auto generating or by notifying user to fill
5401 // the vectors himself
5402 if (mAutoTicks) {
5403 generateAutoTicks();
5404 } else {
5405 emit ticksRequest();
5406 }
5407
5408 visibleTickBounds(mLowestVisibleTick, mHighestVisibleTick);
5409 if (mTickVector.isEmpty()) {
5410 mSubTickVector.clear();
5411 return;
5412 }
5413
5414 // generate subticks between ticks:
5415 mSubTickVector.resize((mTickVector.size() - 1) * mSubTickCount);
5416 if (mSubTickCount > 0) {
5417 double subTickStep = 0;
5418 double subTickPosition = 0;
5419 int subTickIndex = 0;
5420 bool done = false;
5421 int lowTick =
5422 mLowestVisibleTick > 0 ? mLowestVisibleTick - 1 : mLowestVisibleTick;
5423 int highTick = mHighestVisibleTick < mTickVector.size() - 1
5424 ? mHighestVisibleTick + 1
5425 : mHighestVisibleTick;
5426 for (int i = lowTick + 1; i <= highTick; ++i) {
5427 subTickStep = (mTickVector.at(i) - mTickVector.at(i - 1)) /
5428 (double)(mSubTickCount + 1);
5429 for (int k = 1; k <= mSubTickCount; ++k) {
5430 subTickPosition = mTickVector.at(i - 1) + k * subTickStep;
5431 if (subTickPosition < mRange.lower) continue;
5432 if (subTickPosition > mRange.upper) {
5433 done = true;
5434 break;
5435 }
5436 mSubTickVector[subTickIndex] = subTickPosition;
5437 subTickIndex++;
5438 }
5439 if (done) break;
5440 }
5441 mSubTickVector.resize(subTickIndex);
5442 }
5443
5444 // generate tick labels according to tick positions:
5445 if (mAutoTickLabels) {
5446 int vecsize = mTickVector.size();
5447 mTickVectorLabels.resize(vecsize);
5448 if (mTickLabelType == ltNumber) {
5449 for (int i = mLowestVisibleTick; i <= mHighestVisibleTick; ++i)
5450 mTickVectorLabels[i] = mParentPlot->locale().toString(
5451 mTickVector.at(i), mNumberFormatChar.toLatin1(), mNumberPrecision);
5452 } else if (mTickLabelType == ltDateTime) {
5453 for (int i = mLowestVisibleTick; i <= mHighestVisibleTick; ++i) {
5454 #if QT_VERSION < \
5455 QT_VERSION_CHECK( \
5456 4, 7, \
5457 0) // use fromMSecsSinceEpoch function if available, to gain sub-second
5458 // accuracy on tick labels (e.g. for format "hh:mm:ss:zzz")
5459 mTickVectorLabels[i] = mParentPlot->locale().toString(
5460 QDateTime::fromTime_t(mTickVector.at(i)).toTimeSpec(mDateTimeSpec),
5461 mDateTimeFormat);
5462 #else
5463 mTickVectorLabels[i] = mParentPlot->locale().toString(
5464 QDateTime::fromMSecsSinceEpoch(mTickVector.at(i) * 1000)
5465 .toTimeSpec(mDateTimeSpec),
5466 mDateTimeFormat);
5467 #endif
5468 }
5469 }
5470 } else // mAutoTickLabels == false
5471 {
5472 if (mAutoTicks) // ticks generated automatically, but not ticklabels, so
5473 // emit ticksRequest here for labels
5474 {
5475 emit ticksRequest();
5476 }
5477 // make sure provided tick label vector has correct (minimal) length:
5478 if (mTickVectorLabels.size() < mTickVector.size())
5479 mTickVectorLabels.resize(mTickVector.size());
5480 }
5481 }
5482
5483 /*! \internal
5484
5485 If \ref setAutoTicks is set to true, this function is called by \ref
5486 setupTickVectors to generate reasonable tick positions (and subtick count).
5487 The algorithm tries to create approximately <tt>mAutoTickCount</tt> ticks (set
5488 via \ref setAutoTickCount).
5489
5490 If the scale is logarithmic, \ref setAutoTickCount is ignored, and one tick is
5491 generated at every power of the current logarithm base, set via \ref
5492 setScaleLogBase.
5493 */
5494 void QCPAxis::generateAutoTicks() {
5495 if (mScaleType == stLinear) {
5496 if (mAutoTickStep) {
5497 // Generate tick positions according to linear scaling:
5498 mTickStep =
5499 mRange.size() /
5500 (double)(mAutoTickCount +
5501 1e-10); // mAutoTickCount ticks on average, the small
5502 // addition is to prevent jitter on exact integers
5503 double magnitudeFactor = qPow(
5504 10.0,
5505 qFloor(
5506 qLn(mTickStep) /
5507 qLn(10.0))); // get magnitude factor e.g. 0.01, 1, 10, 1000 etc.
5508 double tickStepMantissa = mTickStep / magnitudeFactor;
5509 if (tickStepMantissa < 5) {
5510 // round digit after decimal point to 0.5
5511 mTickStep = (int)(tickStepMantissa * 2) / 2.0 * magnitudeFactor;
5512 } else {
5513 // round to first digit in multiples of 2
5514 mTickStep = (int)(tickStepMantissa / 2.0) * 2.0 * magnitudeFactor;
5515 }
5516 }
5517 if (mAutoSubTicks) mSubTickCount = calculateAutoSubTickCount(mTickStep);
5518 // Generate tick positions according to mTickStep:
5519 qint64 firstStep = floor(
5520 mRange.lower /
5521 mTickStep); // do not use qFloor here, or we'll lose 64 bit precision
5522 qint64 lastStep = ceil(
5523 mRange.upper /
5524 mTickStep); // do not use qCeil here, or we'll lose 64 bit precision
5525 int tickcount = lastStep - firstStep + 1;
5526 if (tickcount < 0) tickcount = 0;
5527 mTickVector.resize(tickcount);
5528 for (int i = 0; i < tickcount; ++i)
5529 mTickVector[i] = (firstStep + i) * mTickStep;
5530 } else // mScaleType == stLogarithmic
5531 {
5532 // Generate tick positions according to logbase scaling:
5533 if (mRange.lower > 0 && mRange.upper > 0) // positive range
5534 {
5535 double lowerMag = basePow(qFloor(baseLog(mRange.lower)));
5536 double currentMag = lowerMag;
5537 mTickVector.clear();
5538 mTickVector.append(currentMag);
5539 while (currentMag < mRange.upper &&
5540 currentMag > 0) // currentMag might be zero for ranges ~1e-300,
5541 // just cancel in that case
5542 {
5543 currentMag *= mScaleLogBase;
5544 mTickVector.append(currentMag);
5545 }
5546 } else if (mRange.lower < 0 && mRange.upper < 0) // negative range
5547 {
5548 double lowerMag = -basePow(qCeil(baseLog(-mRange.lower)));
5549 double currentMag = lowerMag;
5550 mTickVector.clear();
5551 mTickVector.append(currentMag);
5552 while (currentMag < mRange.upper &&
5553 currentMag < 0) // currentMag might be zero for ranges ~1e-300,
5554 // just cancel in that case
5555 {
5556 currentMag /= mScaleLogBase;
5557 mTickVector.append(currentMag);
5558 }
5559 } else // invalid range for logarithmic scale, because lower and upper have
5560 // different sign
5561 {
5562 mTickVector.clear();
5563 qDebug() << Q_FUNC_INFO
5564 << "Invalid range for logarithmic plot: " << mRange.lower << "-"
5565 << mRange.upper;
5566 }
5567 }
5568 }
5569
5570 /*! \internal
5571
5572 Called by generateAutoTicks when \ref setAutoSubTicks is set to true.
5573 Depending on the \a tickStep between two major ticks on the axis, a different
5574 number of sub ticks is appropriate. For Example taking 4 sub ticks for a \a
5575 tickStep of 1 makes more sense than taking 5 sub ticks, because this
5576 corresponds to a sub tick step of 0.2, instead of the less intuitive 0.16667.
5577 Note that a subtick count of 4 means dividing the major tick step into 5
5578 sections.
5579
5580 This is implemented by a hand made lookup for integer tick steps as well as
5581 fractional tick steps with a fractional part of (approximately) 0.5. If a tick
5582 step is different (i.e. has no fractional part close to 0.5), the currently
5583 set sub tick count (\ref setSubTickCount) is returned.
5584 */
5585 int QCPAxis::calculateAutoSubTickCount(double tickStep) const {
5586 int result = mSubTickCount; // default to current setting, if no proper value
5587 // can be found
5588
5589 // get mantissa of tickstep:
5590 double magnitudeFactor = qPow(
5591 10.0,
5592 qFloor(qLn(tickStep) /
5593 qLn(10.0))); // get magnitude factor e.g. 0.01, 1, 10, 1000 etc.
5594 double tickStepMantissa = tickStep / magnitudeFactor;
5595
5596 // separate integer and fractional part of mantissa:
5597 double epsilon = 0.01;
5598 double intPartf;
5599 int intPart;
5600 double fracPart = modf(tickStepMantissa, &intPartf);
5601 intPart = intPartf;
5602
5603 // handle cases with (almost) integer mantissa:
5604 if (fracPart < epsilon || 1.0 - fracPart < epsilon) {
5605 if (1.0 - fracPart < epsilon) ++intPart;
5606 switch (intPart) {
5607 case 1:
5608 result = 4;
5609 break; // 1.0 -> 0.2 substep
5610 case 2:
5611 result = 3;
5612 break; // 2.0 -> 0.5 substep
5613 case 3:
5614 result = 2;
5615 break; // 3.0 -> 1.0 substep
5616 case 4:
5617 result = 3;
5618 break; // 4.0 -> 1.0 substep
5619 case 5:
5620 result = 4;
5621 break; // 5.0 -> 1.0 substep
5622 case 6:
5623 result = 2;
5624 break; // 6.0 -> 2.0 substep
5625 case 7:
5626 result = 6;
5627 break; // 7.0 -> 1.0 substep
5628 case 8:
5629 result = 3;
5630 break; // 8.0 -> 2.0 substep
5631 case 9:
5632 result = 2;
5633 break; // 9.0 -> 3.0 substep
5634 }
5635 } else {
5636 // handle cases with significantly fractional mantissa:
5637 if (qAbs(fracPart - 0.5) < epsilon) // *.5 mantissa
5638 {
5639 switch (intPart) {
5640 case 1:
5641 result = 2;
5642 break; // 1.5 -> 0.5 substep
5643 case 2:
5644 result = 4;
5645 break; // 2.5 -> 0.5 substep
5646 case 3:
5647 result = 4;
5648 break; // 3.5 -> 0.7 substep
5649 case 4:
5650 result = 2;
5651 break; // 4.5 -> 1.5 substep
5652 case 5:
5653 result = 4;
5654 break; // 5.5 -> 1.1 substep (won't occur with autoTickStep from here
5655 // on)
5656 case 6:
5657 result = 4;
5658 break; // 6.5 -> 1.3 substep
5659 case 7:
5660 result = 2;
5661 break; // 7.5 -> 2.5 substep
5662 case 8:
5663 result = 4;
5664 break; // 8.5 -> 1.7 substep
5665 case 9:
5666 result = 4;
5667 break; // 9.5 -> 1.9 substep
5668 }
5669 }
5670 // if mantissa fraction isnt 0.0 or 0.5, don't bother finding good sub tick
5671 // marks, leave default
5672 }
5673
5674 return result;
5675 }
5676
5677 /* inherits documentation from base class */
5678 void QCPAxis::selectEvent(QMouseEvent *event, bool additive,
5679 const QVariant &details,
5680 bool *selectionStateChanged) {
5681 Q_UNUSED(event)
5682 SelectablePart part = details.value<SelectablePart>();
5683 if (mSelectableParts.testFlag(part)) {
5684 SelectableParts selBefore = mSelectedParts;
5685 setSelectedParts(additive ? mSelectedParts ^ part : part);
5686 if (selectionStateChanged)
5687 *selectionStateChanged = mSelectedParts != selBefore;
5688 }
5689 }
5690
5691 /* inherits documentation from base class */
5692 void QCPAxis::deselectEvent(bool *selectionStateChanged) {
5693 SelectableParts selBefore = mSelectedParts;
5694 setSelectedParts(mSelectedParts & ~mSelectableParts);
5695 if (selectionStateChanged)
5696 *selectionStateChanged = mSelectedParts != selBefore;
5697 }
5698
5699 /*! \internal
5700
5701 A convenience function to easily set the QPainter::Antialiased hint on the
5702 provided \a painter before drawing axis lines.
5703
5704 This is the antialiasing state the painter passed to the \ref draw method is
5705 in by default.
5706
5707 This function takes into account the local setting of the antialiasing flag as
5708 well as the overrides set with \ref QCustomPlot::setAntialiasedElements and
5709 \ref QCustomPlot::setNotAntialiasedElements.
5710
5711 \see setAntialiased
5712 */
5713 void QCPAxis::applyDefaultAntialiasingHint(QCPPainter *painter) const {
5714 applyAntialiasingHint(painter, mAntialiased, QCP::aeAxes);
5715 }
5716
5717 /*! \internal
5718
5719 Draws the axis with the specified \a painter, using the internal
5720 QCPAxisPainterPrivate instance.
5721
5722 */
5723 void QCPAxis::draw(QCPPainter *painter) {
5724 const int lowTick = mLowestVisibleTick;
5725 const int highTick = mHighestVisibleTick;
5726 QVector<double> subTickPositions; // the final coordToPixel transformed
5727 // vector passed to QCPAxisPainter
5728 QVector<double> tickPositions; // the final coordToPixel transformed vector
5729 // passed to QCPAxisPainter
5730 QVector<QString> tickLabels; // the final vector passed to QCPAxisPainter
5731 tickPositions.reserve(highTick - lowTick + 1);
5732 tickLabels.reserve(highTick - lowTick + 1);
5733 subTickPositions.reserve(mSubTickVector.size());
5734
5735 if (mTicks) {
5736 for (int i = lowTick; i <= highTick; ++i) {
5737 tickPositions.append(coordToPixel(mTickVector.at(i)));
5738 if (mTickLabels) tickLabels.append(mTickVectorLabels.at(i));
5739 }
5740
5741 if (mSubTickCount > 0) {
5742 const int subTickCount = mSubTickVector.size();
5743 for (int i = 0; i < subTickCount;
5744 ++i) // no need to check bounds because subticks are always only
5745 // created inside current mRange
5746 subTickPositions.append(coordToPixel(mSubTickVector.at(i)));
5747 }
5748 }
5749 // transfer all properties of this axis to QCPAxisPainterPrivate which it
5750 // needs to draw the axis. Note that some axis painter properties are already
5751 // set by direct feed-through with QCPAxis setters
5752 mAxisPainter->type = mAxisType;
5753 mAxisPainter->basePen = getBasePen();
5754 mAxisPainter->labelFont = getLabelFont();
5755 mAxisPainter->labelColor = getLabelColor();
5756 mAxisPainter->label = mLabel;
5757 mAxisPainter->substituteExponent =
5758 mAutoTickLabels && mNumberBeautifulPowers && mTickLabelType == ltNumber;
5759 mAxisPainter->tickPen = getTickPen();
5760 mAxisPainter->subTickPen = getSubTickPen();
5761 mAxisPainter->tickLabelFont = getTickLabelFont();
5762 mAxisPainter->tickLabelColor = getTickLabelColor();
5763 mAxisPainter->axisRect = mAxisRect->rect();
5764 mAxisPainter->viewportRect = mParentPlot->viewport();
5765 mAxisPainter->abbreviateDecimalPowers = mScaleType == stLogarithmic;
5766 mAxisPainter->reversedEndings = mRangeReversed;
5767 mAxisPainter->tickPositions = tickPositions;
5768 mAxisPainter->tickLabels = tickLabels;
5769 mAxisPainter->subTickPositions = subTickPositions;
5770 mAxisPainter->draw(painter);
5771 }
5772
5773 /*! \internal
5774
5775 Returns via \a lowIndex and \a highIndex, which ticks in the current tick
5776 vector are visible in the current range. The return values are indices of the
5777 tick vector, not the positions of the ticks themselves.
5778
5779 The actual use of this function is when an external tick vector is provided,
5780 since it might exceed far beyond the currently displayed range, and would
5781 cause unnecessary calculations e.g. of subticks.
5782
5783 If all ticks are outside the axis range, an inverted range is returned, i.e.
5784 highIndex will be smaller than lowIndex. There is one case, where this
5785 function returns indices that are not really visible in the current axis
5786 range: When the tick spacing is larger than the axis range size and one tick
5787 is below the axis range and the next tick is already above the axis range.
5788 Because in such cases it is usually desirable to know the tick pair, to draw
5789 proper subticks.
5790 */
5791 void QCPAxis::visibleTickBounds(int &lowIndex, int &highIndex) const {
5792 bool lowFound = false;
5793 bool highFound = false;
5794 lowIndex = 0;
5795 highIndex = -1;
5796
5797 for (int i = 0; i < mTickVector.size(); ++i) {
5798 if (mTickVector.at(i) >= mRange.lower) {
5799 lowFound = true;
5800 lowIndex = i;
5801 break;
5802 }
5803 }
5804 for (int i = mTickVector.size() - 1; i >= 0; --i) {
5805 if (mTickVector.at(i) <= mRange.upper) {
5806 highFound = true;
5807 highIndex = i;
5808 break;
5809 }
5810 }
5811
5812 if (!lowFound && highFound)
5813 lowIndex = highIndex + 1;
5814 else if (lowFound && !highFound)
5815 highIndex = lowIndex - 1;
5816 }
5817
5818 /*! \internal
5819
5820 A log function with the base mScaleLogBase, used mostly for coordinate
5821 transforms in logarithmic scales with arbitrary log base. Uses the buffered
5822 mScaleLogBaseLogInv for faster calculation. This is set to
5823 <tt>1.0/qLn(mScaleLogBase)</tt> in \ref setScaleLogBase.
5824
5825 \see basePow, setScaleLogBase, setScaleType
5826 */
5827 double QCPAxis::baseLog(double value) const {
5828 return qLn(value) * mScaleLogBaseLogInv;
5829 }
5830
5831 /*! \internal
5832
5833 A power function with the base mScaleLogBase, used mostly for coordinate
5834 transforms in logarithmic scales with arbitrary log base.
5835
5836 \see baseLog, setScaleLogBase, setScaleType
5837 */
5838 double QCPAxis::basePow(double value) const {
5839 return qPow(mScaleLogBase, value);
5840 }
5841
5842 /*! \internal
5843
5844 Returns the pen that is used to draw the axis base line. Depending on the
5845 selection state, this is either mSelectedBasePen or mBasePen.
5846 */
5847 QPen QCPAxis::getBasePen() const {
5848 return mSelectedParts.testFlag(spAxis) ? mSelectedBasePen : mBasePen;
5849 }
5850
5851 /*! \internal
5852
5853 Returns the pen that is used to draw the (major) ticks. Depending on the
5854 selection state, this is either mSelectedTickPen or mTickPen.
5855 */
5856 QPen QCPAxis::getTickPen() const {
5857 return mSelectedParts.testFlag(spAxis) ? mSelectedTickPen : mTickPen;
5858 }
5859
5860 /*! \internal
5861
5862 Returns the pen that is used to draw the subticks. Depending on the selection
5863 state, this is either mSelectedSubTickPen or mSubTickPen.
5864 */
5865 QPen QCPAxis::getSubTickPen() const {
5866 return mSelectedParts.testFlag(spAxis) ? mSelectedSubTickPen : mSubTickPen;
5867 }
5868
5869 /*! \internal
5870
5871 Returns the font that is used to draw the tick labels. Depending on the
5872 selection state, this is either mSelectedTickLabelFont or mTickLabelFont.
5873 */
5874 QFont QCPAxis::getTickLabelFont() const {
5875 return mSelectedParts.testFlag(spTickLabels) ? mSelectedTickLabelFont
5876 : mTickLabelFont;
5877 }
5878
5879 /*! \internal
5880
5881 Returns the font that is used to draw the axis label. Depending on the
5882 selection state, this is either mSelectedLabelFont or mLabelFont.
5883 */
5884 QFont QCPAxis::getLabelFont() const {
5885 return mSelectedParts.testFlag(spAxisLabel) ? mSelectedLabelFont : mLabelFont;
5886 }
5887
5888 /*! \internal
5889
5890 Returns the color that is used to draw the tick labels. Depending on the
5891 selection state, this is either mSelectedTickLabelColor or mTickLabelColor.
5892 */
5893 QColor QCPAxis::getTickLabelColor() const {
5894 return mSelectedParts.testFlag(spTickLabels) ? mSelectedTickLabelColor
5895 : mTickLabelColor;
5896 }
5897
5898 /*! \internal
5899
5900 Returns the color that is used to draw the axis label. Depending on the
5901 selection state, this is either mSelectedLabelColor or mLabelColor.
5902 */
5903 QColor QCPAxis::getLabelColor() const {
5904 return mSelectedParts.testFlag(spAxisLabel) ? mSelectedLabelColor
5905 : mLabelColor;
5906 }
5907
5908 /*! \internal
5909
5910 Returns the appropriate outward margin for this axis. It is needed if \ref
5911 QCPAxisRect::setAutoMargins is set to true on the parent axis rect. An axis
5912 with axis type \ref atLeft will return an appropriate left margin, \ref
5913 atBottom will return an appropriate bottom margin and so forth. For the
5914 calculation, this function goes through similar steps as \ref draw, so
5915 changing one function likely requires the modification of the other one as
5916 well.
5917
5918 The margin consists of the outward tick length, tick label padding, tick label
5919 size, label padding, label size, and padding.
5920
5921 The margin is cached internally, so repeated calls while leaving the axis
5922 range, fonts, etc. unchanged are very fast.
5923 */
5924 int QCPAxis::calculateMargin() {
5925 if (!mVisible) // if not visible, directly return 0, don't cache 0 because we
5926 // can't react to setVisible in QCPAxis
5927 return 0;
5928
5929 if (mCachedMarginValid) return mCachedMargin;
5930
5931 // run through similar steps as QCPAxis::draw, and caluclate margin needed to
5932 // fit axis and its labels
5933 int margin = 0;
5934
5935 int lowTick, highTick;
5936 visibleTickBounds(lowTick, highTick);
5937 QVector<double> tickPositions; // the final coordToPixel transformed vector
5938 // passed to QCPAxisPainter
5939 QVector<QString> tickLabels; // the final vector passed to QCPAxisPainter
5940 tickPositions.reserve(highTick - lowTick + 1);
5941 tickLabels.reserve(highTick - lowTick + 1);
5942 if (mTicks) {
5943 for (int i = lowTick; i <= highTick; ++i) {
5944 tickPositions.append(coordToPixel(mTickVector.at(i)));
5945 if (mTickLabels) tickLabels.append(mTickVectorLabels.at(i));
5946 }
5947 }
5948 // transfer all properties of this axis to QCPAxisPainterPrivate which it
5949 // needs to calculate the size. Note that some axis painter properties are
5950 // already set by direct feed-through with QCPAxis setters
5951 mAxisPainter->type = mAxisType;
5952 mAxisPainter->labelFont = getLabelFont();
5953 mAxisPainter->label = mLabel;
5954 mAxisPainter->tickLabelFont = mTickLabelFont;
5955 mAxisPainter->axisRect = mAxisRect->rect();
5956 mAxisPainter->viewportRect = mParentPlot->viewport();
5957 mAxisPainter->tickPositions = tickPositions;
5958 mAxisPainter->tickLabels = tickLabels;
5959 margin += mAxisPainter->size();
5960 margin += mPadding;
5961
5962 mCachedMargin = margin;
5963 mCachedMarginValid = true;
5964 return margin;
5965 }
5966
5967 /* inherits documentation from base class */
5968 QCP::Interaction QCPAxis::selectionCategory() const { return QCP::iSelectAxes; }
5969
5970 ////////////////////////////////////////////////////////////////////////////////////////////////////
5971 //////////////////// QCPAxisPainterPrivate
5972 ////////////////////////////////////////////////////////////////////////////////////////////////////
5973
5974 /*! \class QCPAxisPainterPrivate
5975
5976 \internal
5977 \brief (Private)
5978
5979 This is a private class and not part of the public QCustomPlot interface.
5980
5981 It is used by QCPAxis to do the low-level drawing of axis backbone, tick
5982 marks, tick labels and axis label. It also buffers the labels to reduce replot
5983 times. The parameters are configured by directly accessing the public member
5984 variables.
5985 */
5986
5987 /*!
5988 Constructs a QCPAxisPainterPrivate instance. Make sure to not create a new
5989 instance on every redraw, to utilize the caching mechanisms.
5990 */
5991 QCPAxisPainterPrivate::QCPAxisPainterPrivate(QCustomPlot *parentPlot)
5992 : type(QCPAxis::atLeft),
5993 basePen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
5994 lowerEnding(QCPLineEnding::esNone),
5995 upperEnding(QCPLineEnding::esNone),
5996 labelPadding(0),
5997 tickLabelPadding(0),
5998 tickLabelRotation(0),
5999 tickLabelSide(QCPAxis::lsOutside),
6000 substituteExponent(true),
6001 numberMultiplyCross(false),
6002 tickLengthIn(5),
6003 tickLengthOut(0),
6004 subTickLengthIn(2),
6005 subTickLengthOut(0),
6006 tickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
6007 subTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
6008 offset(0),
6009 abbreviateDecimalPowers(false),
6010 reversedEndings(false),
6011 mParentPlot(parentPlot),
6012 mLabelCache(16) // cache at most 16 (tick) labels
6013 {}
6014
6015 QCPAxisPainterPrivate::~QCPAxisPainterPrivate() {}
6016
6017 /*! \internal
6018
6019 Draws the axis with the specified \a painter.
6020
6021 The selection boxes (mAxisSelectionBox, mTickLabelsSelectionBox,
6022 mLabelSelectionBox) are set here, too.
6023 */
6024 void QCPAxisPainterPrivate::draw(QCPPainter *painter) {
6025 QByteArray newHash = generateLabelParameterHash();
6026 if (newHash != mLabelParameterHash) {
6027 mLabelCache.clear();
6028 mLabelParameterHash = newHash;
6029 }
6030
6031 QPoint origin;
6032 switch (type) {
6033 case QCPAxis::atLeft:
6034 origin = axisRect.bottomLeft() + QPoint(-offset, 0);
6035 break;
6036 case QCPAxis::atRight:
6037 origin = axisRect.bottomRight() + QPoint(+offset, 0);
6038 break;
6039 case QCPAxis::atTop:
6040 origin = axisRect.topLeft() + QPoint(0, -offset);
6041 break;
6042 case QCPAxis::atBottom:
6043 origin = axisRect.bottomLeft() + QPoint(0, +offset);
6044 break;
6045 }
6046
6047 double xCor = 0,
6048 yCor = 0; // paint system correction, for pixel exact matches (affects
6049 // baselines and ticks of top/right axes)
6050 switch (type) {
6051 case QCPAxis::atTop:
6052 yCor = -1;
6053 break;
6054 case QCPAxis::atRight:
6055 xCor = 1;
6056 break;
6057 default:
6058 break;
6059 }
6060 int margin = 0;
6061 // draw baseline:
6062 QLineF baseLine;
6063 painter->setPen(basePen);
6064 if (QCPAxis::orientation(type) == Qt::Horizontal)
6065 baseLine.setPoints(origin + QPointF(xCor, yCor),
6066 origin + QPointF(axisRect.width() + xCor, yCor));
6067 else
6068 baseLine.setPoints(origin + QPointF(xCor, yCor),
6069 origin + QPointF(xCor, -axisRect.height() + yCor));
6070 if (reversedEndings)
6071 baseLine = QLineF(baseLine.p2(),
6072 baseLine.p1()); // won't make a difference for line
6073 // itself, but for line endings later
6074 painter->drawLine(baseLine);
6075
6076 // draw ticks:
6077 if (!tickPositions.isEmpty()) {
6078 painter->setPen(tickPen);
6079 int tickDir = (type == QCPAxis::atBottom || type == QCPAxis::atRight)
6080 ? -1
6081 : 1; // direction of ticks ("inward" is right for left
6082 // axis and left for right axis)
6083 if (QCPAxis::orientation(type) == Qt::Horizontal) {
6084 for (int i = 0; i < tickPositions.size(); ++i)
6085 painter->drawLine(QLineF(tickPositions.at(i) + xCor,
6086 origin.y() - tickLengthOut * tickDir + yCor,
6087 tickPositions.at(i) + xCor,
6088 origin.y() + tickLengthIn * tickDir + yCor));
6089 } else {
6090 for (int i = 0; i < tickPositions.size(); ++i)
6091 painter->drawLine(QLineF(origin.x() - tickLengthOut * tickDir + xCor,
6092 tickPositions.at(i) + yCor,
6093 origin.x() + tickLengthIn * tickDir + xCor,
6094 tickPositions.at(i) + yCor));
6095 }
6096 }
6097
6098 // draw subticks:
6099 if (!subTickPositions.isEmpty()) {
6100 painter->setPen(subTickPen);
6101 // direction of ticks ("inward" is right for left axis and left for right
6102 // axis)
6103 int tickDir =
6104 (type == QCPAxis::atBottom || type == QCPAxis::atRight) ? -1 : 1;
6105 if (QCPAxis::orientation(type) == Qt::Horizontal) {
6106 for (int i = 0; i < subTickPositions.size(); ++i)
6107 painter->drawLine(
6108 QLineF(subTickPositions.at(i) + xCor,
6109 origin.y() - subTickLengthOut * tickDir + yCor,
6110 subTickPositions.at(i) + xCor,
6111 origin.y() + subTickLengthIn * tickDir + yCor));
6112 } else {
6113 for (int i = 0; i < subTickPositions.size(); ++i)
6114 painter->drawLine(QLineF(origin.x() - subTickLengthOut * tickDir + xCor,
6115 subTickPositions.at(i) + yCor,
6116 origin.x() + subTickLengthIn * tickDir + xCor,
6117 subTickPositions.at(i) + yCor));
6118 }
6119 }
6120 margin += qMax(0, qMax(tickLengthOut, subTickLengthOut));
6121
6122 // draw axis base endings:
6123 bool antialiasingBackup = painter->antialiasing();
6124 painter->setAntialiasing(true); // always want endings to be antialiased,
6125 // even if base and ticks themselves aren't
6126 painter->setBrush(QBrush(basePen.color()));
6127 QVector2D baseLineVector(baseLine.dx(), baseLine.dy());
6128 if (lowerEnding.style() != QCPLineEnding::esNone)
6129 lowerEnding.draw(
6130 painter,
6131 QVector2D(baseLine.p1()) - baseLineVector.normalized() *
6132 lowerEnding.realLength() *
6133 (lowerEnding.inverted() ? -1 : 1),
6134 -baseLineVector);
6135 if (upperEnding.style() != QCPLineEnding::esNone)
6136 upperEnding.draw(
6137 painter,
6138 QVector2D(baseLine.p2()) + baseLineVector.normalized() *
6139 upperEnding.realLength() *
6140 (upperEnding.inverted() ? -1 : 1),
6141 baseLineVector);
6142 painter->setAntialiasing(antialiasingBackup);
6143
6144 // tick labels:
6145 QRect oldClipRect;
6146 if (tickLabelSide ==
6147 QCPAxis::lsInside) // if using inside labels, clip them to the axis rect
6148 {
6149 oldClipRect = painter->clipRegion().boundingRect();
6150 painter->setClipRect(axisRect);
6151 }
6152 QSize tickLabelsSize(
6153 0,
6154 0); // size of largest tick label, for offset calculation of axis label
6155 if (!tickLabels.isEmpty()) {
6156 if (tickLabelSide == QCPAxis::lsOutside) margin += tickLabelPadding;
6157 painter->setFont(tickLabelFont);
6158 painter->setPen(QPen(tickLabelColor));
6159 const int maxLabelIndex = qMin(tickPositions.size(), tickLabels.size());
6160 int distanceToAxis = margin;
6161 if (tickLabelSide == QCPAxis::lsInside)
6162 distanceToAxis =
6163 -(qMax(tickLengthIn, subTickLengthIn) + tickLabelPadding);
6164 for (int i = 0; i < maxLabelIndex; ++i)
6165 placeTickLabel(painter, tickPositions.at(i), distanceToAxis,
6166 tickLabels.at(i), &tickLabelsSize);
6167 if (tickLabelSide == QCPAxis::lsOutside)
6168 margin += (QCPAxis::orientation(type) == Qt::Horizontal)
6169 ? tickLabelsSize.height()
6170 : tickLabelsSize.width();
6171 }
6172 if (tickLabelSide == QCPAxis::lsInside) painter->setClipRect(oldClipRect);
6173
6174 // axis label:
6175 QRect labelBounds;
6176 if (!label.isEmpty()) {
6177 margin += labelPadding;
6178 painter->setFont(labelFont);
6179 painter->setPen(QPen(labelColor));
6180 labelBounds = painter->fontMetrics().boundingRect(0, 0, 0, 0,
6181 Qt::TextDontClip, label);
6182 if (type == QCPAxis::atLeft) {
6183 QTransform oldTransform = painter->transform();
6184 painter->translate((origin.x() - margin - labelBounds.height()),
6185 origin.y());
6186 painter->rotate(-90);
6187 painter->drawText(0, 0, axisRect.height(), labelBounds.height(),
6188 Qt::TextDontClip | Qt::AlignCenter, label);
6189 painter->setTransform(oldTransform);
6190 } else if (type == QCPAxis::atRight) {
6191 QTransform oldTransform = painter->transform();
6192 painter->translate((origin.x() + margin + labelBounds.height()),
6193 origin.y() - axisRect.height());
6194 painter->rotate(90);
6195 painter->drawText(0, 0, axisRect.height(), labelBounds.height(),
6196 Qt::TextDontClip | Qt::AlignCenter, label);
6197 painter->setTransform(oldTransform);
6198 } else if (type == QCPAxis::atTop)
6199 painter->drawText(origin.x(), origin.y() - margin - labelBounds.height(),
6200 axisRect.width(), labelBounds.height(),
6201 Qt::TextDontClip | Qt::AlignCenter, label);
6202 else if (type == QCPAxis::atBottom)
6203 painter->drawText(origin.x(), origin.y() + margin, axisRect.width(),
6204 labelBounds.height(),
6205 Qt::TextDontClip | Qt::AlignCenter, label);
6206 }
6207
6208 // set selection boxes:
6209 int selectionTolerance = 0;
6210 if (mParentPlot)
6211 selectionTolerance = mParentPlot->selectionTolerance();
6212 else
6213 qDebug() << Q_FUNC_INFO << "mParentPlot is null";
6214 int selAxisOutSize =
6215 qMax(qMax(tickLengthOut, subTickLengthOut), selectionTolerance);
6216 int selAxisInSize = selectionTolerance;
6217 int selTickLabelSize;
6218 int selTickLabelOffset;
6219 if (tickLabelSide == QCPAxis::lsOutside) {
6220 selTickLabelSize =
6221 (QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height()
6222 : tickLabelsSize.width());
6223 selTickLabelOffset =
6224 qMax(tickLengthOut, subTickLengthOut) + tickLabelPadding;
6225 } else {
6226 selTickLabelSize = -(QCPAxis::orientation(type) == Qt::Horizontal
6227 ? tickLabelsSize.height()
6228 : tickLabelsSize.width());
6229 selTickLabelOffset =
6230 -(qMax(tickLengthIn, subTickLengthIn) + tickLabelPadding);
6231 }
6232 int selLabelSize = labelBounds.height();
6233 int selLabelOffset =
6234 qMax(tickLengthOut, subTickLengthOut) +
6235 (!tickLabels.isEmpty() && tickLabelSide == QCPAxis::lsOutside
6236 ? tickLabelPadding + selTickLabelSize
6237 : 0) +
6238 labelPadding;
6239 if (type == QCPAxis::atLeft) {
6240 mAxisSelectionBox.setCoords(origin.x() - selAxisOutSize, axisRect.top(),
6241 origin.x() + selAxisInSize, axisRect.bottom());
6242 mTickLabelsSelectionBox.setCoords(
6243 origin.x() - selTickLabelOffset - selTickLabelSize, axisRect.top(),
6244 origin.x() - selTickLabelOffset, axisRect.bottom());
6245 mLabelSelectionBox.setCoords(origin.x() - selLabelOffset - selLabelSize,
6246 axisRect.top(), origin.x() - selLabelOffset,
6247 axisRect.bottom());
6248 } else if (type == QCPAxis::atRight) {
6249 mAxisSelectionBox.setCoords(origin.x() - selAxisInSize, axisRect.top(),
6250 origin.x() + selAxisOutSize, axisRect.bottom());
6251 mTickLabelsSelectionBox.setCoords(
6252 origin.x() + selTickLabelOffset + selTickLabelSize, axisRect.top(),
6253 origin.x() + selTickLabelOffset, axisRect.bottom());
6254 mLabelSelectionBox.setCoords(origin.x() + selLabelOffset + selLabelSize,
6255 axisRect.top(), origin.x() + selLabelOffset,
6256 axisRect.bottom());
6257 } else if (type == QCPAxis::atTop) {
6258 mAxisSelectionBox.setCoords(axisRect.left(), origin.y() - selAxisOutSize,
6259 axisRect.right(), origin.y() + selAxisInSize);
6260 mTickLabelsSelectionBox.setCoords(
6261 axisRect.left(), origin.y() - selTickLabelOffset - selTickLabelSize,
6262 axisRect.right(), origin.y() - selTickLabelOffset);
6263 mLabelSelectionBox.setCoords(axisRect.left(),
6264 origin.y() - selLabelOffset - selLabelSize,
6265 axisRect.right(), origin.y() - selLabelOffset);
6266 } else if (type == QCPAxis::atBottom) {
6267 mAxisSelectionBox.setCoords(axisRect.left(), origin.y() - selAxisInSize,
6268 axisRect.right(), origin.y() + selAxisOutSize);
6269 mTickLabelsSelectionBox.setCoords(
6270 axisRect.left(), origin.y() + selTickLabelOffset + selTickLabelSize,
6271 axisRect.right(), origin.y() + selTickLabelOffset);
6272 mLabelSelectionBox.setCoords(axisRect.left(),
6273 origin.y() + selLabelOffset + selLabelSize,
6274 axisRect.right(), origin.y() + selLabelOffset);
6275 }
6276 mAxisSelectionBox = mAxisSelectionBox.normalized();
6277 mTickLabelsSelectionBox = mTickLabelsSelectionBox.normalized();
6278 mLabelSelectionBox = mLabelSelectionBox.normalized();
6279 // draw hitboxes for debug purposes:
6280 // painter->setBrush(Qt::NoBrush);
6281 // painter->drawRects(QVector<QRect>() << mAxisSelectionBox <<
6282 // mTickLabelsSelectionBox << mLabelSelectionBox);
6283 }
6284
6285 /*! \internal
6286
6287 Returns the size ("margin" in QCPAxisRect context, so measured perpendicular
6288 to the axis backbone direction) needed to fit the axis.
6289 */
6290 int QCPAxisPainterPrivate::size() const {
6291 int result = 0;
6292
6293 // get length of tick marks pointing outwards:
6294 if (!tickPositions.isEmpty())
6295 result += qMax(0, qMax(tickLengthOut, subTickLengthOut));
6296
6297 // calculate size of tick labels:
6298 if (tickLabelSide == QCPAxis::lsOutside) {
6299 QSize tickLabelsSize(0, 0);
6300 if (!tickLabels.isEmpty()) {
6301 for (int i = 0; i < tickLabels.size(); ++i)
6302 getMaxTickLabelSize(tickLabelFont, tickLabels.at(i), &tickLabelsSize);
6303 result += QCPAxis::orientation(type) == Qt::Horizontal
6304 ? tickLabelsSize.height()
6305 : tickLabelsSize.width();
6306 result += tickLabelPadding;
6307 }
6308 }
6309
6310 // calculate size of axis label (only height needed, because left/right labels
6311 // are rotated by 90 degrees):
6312 if (!label.isEmpty()) {
6313 QFontMetrics fontMetrics(labelFont);
6314 QRect bounds;
6315 bounds = fontMetrics.boundingRect(
6316 0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter | Qt::AlignVCenter,
6317 label);
6318 result += bounds.height() + labelPadding;
6319 }
6320
6321 return result;
6322 }
6323
6324 /*! \internal
6325
6326 Clears the internal label cache. Upon the next \ref draw, all labels will be
6327 created new. This method is called automatically in \ref draw, if any
6328 parameters have changed that invalidate the cached labels, such as font,
6329 color, etc.
6330 */
6331 void QCPAxisPainterPrivate::clearCache() { mLabelCache.clear(); }
6332
6333 /*! \internal
6334
6335 Returns a hash that allows uniquely identifying whether the label parameters
6336 have changed such that the cached labels must be refreshed (\ref clearCache).
6337 It is used in \ref draw. If the return value of this method hasn't changed
6338 since the last redraw, the respective label parameters haven't changed and
6339 cached labels may be used.
6340 */
6341 QByteArray QCPAxisPainterPrivate::generateLabelParameterHash() const {
6342 QByteArray result;
6343 result.append(QByteArray::number(tickLabelRotation));
6344 result.append(QByteArray::number((int)tickLabelSide));
6345 result.append(QByteArray::number((int)substituteExponent));
6346 result.append(QByteArray::number((int)numberMultiplyCross));
6347 result.append(tickLabelColor.name().toLatin1() +
6348 QByteArray::number(tickLabelColor.alpha(), 16));
6349 result.append(tickLabelFont.toString().toLatin1());
6350 return result;
6351 }
6352
6353 /*! \internal
6354
6355 Draws a single tick label with the provided \a painter, utilizing the internal
6356 label cache to significantly speed up drawing of labels that were drawn in
6357 previous calls. The tick label is always bound to an axis, the distance to the
6358 axis is controllable via \a distanceToAxis in pixels. The pixel position in
6359 the axis direction is passed in the \a position parameter. Hence for the
6360 bottom axis, \a position would indicate the horizontal pixel position (not
6361 coordinate), at which the label should be drawn.
6362
6363 In order to later draw the axis label in a place that doesn't overlap with the
6364 tick labels, the largest tick label size is needed. This is acquired by
6365 passing a \a tickLabelsSize to the \ref drawTickLabel calls during the process
6366 of drawing all tick labels of one axis. In every call, \a tickLabelsSize is
6367 expanded, if the drawn label exceeds the value \a tickLabelsSize currently
6368 holds.
6369
6370 The label is drawn with the font and pen that are currently set on the \a
6371 painter. To draw superscripted powers, the font is temporarily made smaller by
6372 a fixed factor (see \ref getTickLabelData).
6373 */
6374 void QCPAxisPainterPrivate::placeTickLabel(QCPPainter *painter, double position,
6375 int distanceToAxis,
6376 const QString &text,
6377 QSize *tickLabelsSize) {
6378 // warning: if you change anything here, also adapt getMaxTickLabelSize()
6379 // accordingly!
6380 if (text.isEmpty()) return;
6381 QSize finalSize;
6382 QPointF labelAnchor;
6383 switch (type) {
6384 case QCPAxis::atLeft:
6385 labelAnchor =
6386 QPointF(axisRect.left() - distanceToAxis - offset, position);
6387 break;
6388 case QCPAxis::atRight:
6389 labelAnchor =
6390 QPointF(axisRect.right() + distanceToAxis + offset, position);
6391 break;
6392 case QCPAxis::atTop:
6393 labelAnchor = QPointF(position, axisRect.top() - distanceToAxis - offset);
6394 break;
6395 case QCPAxis::atBottom:
6396 labelAnchor =
6397 QPointF(position, axisRect.bottom() + distanceToAxis + offset);
6398 break;
6399 }
6400 if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) &&
6401 !painter->modes().testFlag(
6402 QCPPainter::pmNoCaching)) // label caching enabled
6403 {
6404 CachedLabel *cachedLabel =
6405 mLabelCache.take(text); // attempt to get label from cache
6406 if (!cachedLabel) // no cached label existed, create it
6407 {
6408 cachedLabel = new CachedLabel;
6409 TickLabelData labelData = getTickLabelData(painter->font(), text);
6410 cachedLabel->offset = getTickLabelDrawOffset(labelData) +
6411 labelData.rotatedTotalBounds.topLeft();
6412 cachedLabel->pixmap = QPixmap(labelData.rotatedTotalBounds.size());
6413 cachedLabel->pixmap.fill(Qt::transparent);
6414 QCPPainter cachePainter(&cachedLabel->pixmap);
6415 cachePainter.setPen(painter->pen());
6416 drawTickLabel(&cachePainter, -labelData.rotatedTotalBounds.topLeft().x(),
6417 -labelData.rotatedTotalBounds.topLeft().y(), labelData);
6418 }
6419 // if label would be partly clipped by widget border on sides, don't draw it
6420 // (only for outside tick labels):
6421 bool labelClippedByBorder = false;
6422 if (tickLabelSide == QCPAxis::lsOutside) {
6423 if (QCPAxis::orientation(type) == Qt::Horizontal)
6424 labelClippedByBorder =
6425 labelAnchor.x() + cachedLabel->offset.x() +
6426 cachedLabel->pixmap.width() >
6427 viewportRect.right() ||
6428 labelAnchor.x() + cachedLabel->offset.x() < viewportRect.left();
6429 else
6430 labelClippedByBorder =
6431 labelAnchor.y() + cachedLabel->offset.y() +
6432 cachedLabel->pixmap.height() >
6433 viewportRect.bottom() ||
6434 labelAnchor.y() + cachedLabel->offset.y() < viewportRect.top();
6435 }
6436 if (!labelClippedByBorder) {
6437 painter->drawPixmap(labelAnchor + cachedLabel->offset,
6438 cachedLabel->pixmap);
6439 finalSize = cachedLabel->pixmap.size();
6440 }
6441 mLabelCache.insert(text,
6442 cachedLabel); // return label to cache or insert for the
6443 // first time if newly created
6444 } else // label caching disabled, draw text directly on surface:
6445 {
6446 TickLabelData labelData = getTickLabelData(painter->font(), text);
6447 QPointF finalPosition = labelAnchor + getTickLabelDrawOffset(labelData);
6448 // if label would be partly clipped by widget border on sides, don't draw it
6449 // (only for outside tick labels):
6450 bool labelClippedByBorder = false;
6451 if (tickLabelSide == QCPAxis::lsOutside) {
6452 if (QCPAxis::orientation(type) == Qt::Horizontal)
6453 labelClippedByBorder =
6454 finalPosition.x() + (labelData.rotatedTotalBounds.width() +
6455 labelData.rotatedTotalBounds.left()) >
6456 viewportRect.right() ||
6457 finalPosition.x() + labelData.rotatedTotalBounds.left() <
6458 viewportRect.left();
6459 else
6460 labelClippedByBorder =
6461 finalPosition.y() + (labelData.rotatedTotalBounds.height() +
6462 labelData.rotatedTotalBounds.top()) >
6463 viewportRect.bottom() ||
6464 finalPosition.y() + labelData.rotatedTotalBounds.top() <
6465 viewportRect.top();
6466 }
6467 if (!labelClippedByBorder) {
6468 drawTickLabel(painter, finalPosition.x(), finalPosition.y(), labelData);
6469 finalSize = labelData.rotatedTotalBounds.size();
6470 }
6471 }
6472
6473 // expand passed tickLabelsSize if current tick label is larger:
6474 if (finalSize.width() > tickLabelsSize->width())
6475 tickLabelsSize->setWidth(finalSize.width());
6476 if (finalSize.height() > tickLabelsSize->height())
6477 tickLabelsSize->setHeight(finalSize.height());
6478 }
6479
6480 /*! \internal
6481
6482 This is a \ref placeTickLabel helper function.
6483
6484 Draws the tick label specified in \a labelData with \a painter at the pixel
6485 positions \a x and \a y. This function is used by \ref placeTickLabel to
6486 create new tick labels for the cache, or to directly draw the labels on the
6487 QCustomPlot surface when label caching is disabled, i.e. when
6488 QCP::phCacheLabels plotting hint is not set.
6489 */
6490 void QCPAxisPainterPrivate::drawTickLabel(
6491 QCPPainter *painter, double x, double y,
6492 const TickLabelData &labelData) const {
6493 // backup painter settings that we're about to change:
6494 QTransform oldTransform = painter->transform();
6495 QFont oldFont = painter->font();
6496
6497 // transform painter to position/rotation:
6498 painter->translate(x, y);
6499 if (!qFuzzyIsNull(tickLabelRotation)) painter->rotate(tickLabelRotation);
6500
6501 // draw text:
6502 if (!labelData.expPart
6503 .isEmpty()) // indicator that beautiful powers must be used
6504 {
6505 painter->setFont(labelData.baseFont);
6506 painter->drawText(0, 0, 0, 0, Qt::TextDontClip, labelData.basePart);
6507 painter->setFont(labelData.expFont);
6508 painter->drawText(labelData.baseBounds.width() + 1, 0,
6509 labelData.expBounds.width(), labelData.expBounds.height(),
6510 Qt::TextDontClip, labelData.expPart);
6511 } else {
6512 painter->setFont(labelData.baseFont);
6513 painter->drawText(0, 0, labelData.totalBounds.width(),
6514 labelData.totalBounds.height(),
6515 Qt::TextDontClip | Qt::AlignHCenter, labelData.basePart);
6516 }
6517
6518 // reset painter settings to what it was before:
6519 painter->setTransform(oldTransform);
6520 painter->setFont(oldFont);
6521 }
6522
6523 /*! \internal
6524
6525 This is a \ref placeTickLabel helper function.
6526
6527 Transforms the passed \a text and \a font to a tickLabelData structure that
6528 can then be further processed by \ref getTickLabelDrawOffset and \ref
6529 drawTickLabel. It splits the text into base and exponent if necessary (member
6530 substituteExponent) and calculates appropriate bounding boxes.
6531 */
6532 QCPAxisPainterPrivate::TickLabelData QCPAxisPainterPrivate::getTickLabelData(
6533 const QFont &font, const QString &text) const {
6534 TickLabelData result;
6535
6536 // determine whether beautiful decimal powers should be used
6537 bool useBeautifulPowers = false;
6538 int ePos = -1; // first index of exponent part, text before that will be
6539 // basePart, text until eLast will be expPart
6540 int eLast = -1; // last index of exponent part, rest of text after this will
6541 // be suffixPart
6542 if (substituteExponent) {
6543 ePos = text.indexOf(QLatin1Char('e'));
6544 if (ePos > 0 && text.at(ePos - 1).isDigit()) {
6545 eLast = ePos;
6546 while (eLast + 1 < text.size() &&
6547 (text.at(eLast + 1) == QLatin1Char('+') ||
6548 text.at(eLast + 1) == QLatin1Char('-') ||
6549 text.at(eLast + 1).isDigit()))
6550 ++eLast;
6551 if (eLast > ePos) // only if also to right of 'e' is a digit/+/-
6552 // interpret it as beautifiable power
6553 useBeautifulPowers = true;
6554 }
6555 }
6556
6557 // calculate text bounding rects and do string preparation for beautiful
6558 // decimal powers:
6559 result.baseFont = font;
6560 if (result.baseFont.pointSizeF() >
6561 0) // might return -1 if specified with setPixelSize, in that case we
6562 // can't do correction in next line
6563 result.baseFont.setPointSizeF(
6564 result.baseFont.pointSizeF() +
6565 0.05); // QFontMetrics.boundingRect has a bug for exact point sizes
6566 // that make the results oscillate due to internal rounding
6567 if (useBeautifulPowers) {
6568 // split text into parts of number/symbol that will be drawn normally and
6569 // part that will be drawn as exponent:
6570 result.basePart = text.left(ePos);
6571 // in log scaling, we want to turn "1*10^n" into "10^n", else add
6572 // multiplication sign and decimal base:
6573 if (abbreviateDecimalPowers && result.basePart == QLatin1String("1"))
6574 result.basePart = QLatin1String("10");
6575 else
6576 result.basePart +=
6577 (numberMultiplyCross ? QString(QChar(215)) : QString(QChar(183))) +
6578 QLatin1String("10");
6579 result.expPart = text.mid(ePos + 1);
6580 // clip "+" and leading zeros off expPart:
6581 while (result.expPart.length() > 2 &&
6582 result.expPart.at(1) ==
6583 QLatin1Char('0')) // length > 2 so we leave one zero when
6584 // numberFormatChar is 'e'
6585 result.expPart.remove(1, 1);
6586 if (!result.expPart.isEmpty() && result.expPart.at(0) == QLatin1Char('+'))
6587 result.expPart.remove(0, 1);
6588 // prepare smaller font for exponent:
6589 result.expFont = font;
6590 if (result.expFont.pointSize() > 0)
6591 result.expFont.setPointSize(result.expFont.pointSize() * 0.75);
6592 else
6593 result.expFont.setPixelSize(result.expFont.pixelSize() * 0.75);
6594 // calculate bounding rects of base part, exponent part and total one:
6595 result.baseBounds =
6596 QFontMetrics(result.baseFont)
6597 .boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.basePart);
6598 result.expBounds =
6599 QFontMetrics(result.expFont)
6600 .boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.expPart);
6601 result.totalBounds = result.baseBounds.adjusted(
6602 0, 0, result.expBounds.width() + 2,
6603 0); // +2 consists of the 1 pixel spacing between base and exponent
6604 // (see drawTickLabel) and an extra pixel to include AA
6605 } else // useBeautifulPowers == false
6606 {
6607 result.basePart = text;
6608 result.totalBounds =
6609 QFontMetrics(result.baseFont)
6610 .boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter,
6611 result.basePart);
6612 }
6613 result.totalBounds.moveTopLeft(QPoint(
6614 0, 0)); // want bounding box aligned top left at origin, independent of
6615 // how it was created, to make further processing simpler
6616
6617 // calculate possibly different bounding rect after rotation:
6618 result.rotatedTotalBounds = result.totalBounds;
6619 if (!qFuzzyIsNull(tickLabelRotation)) {
6620 QTransform transform;
6621 transform.rotate(tickLabelRotation);
6622 result.rotatedTotalBounds = transform.mapRect(result.rotatedTotalBounds);
6623 }
6624
6625 return result;
6626 }
6627
6628 /*! \internal
6629
6630 This is a \ref placeTickLabel helper function.
6631
6632 Calculates the offset at which the top left corner of the specified tick label
6633 shall be drawn. The offset is relative to a point right next to the tick the
6634 label belongs to.
6635
6636 This function is thus responsible for e.g. centering tick labels under ticks
6637 and positioning them appropriately when they are rotated.
6638 */
6639 QPointF QCPAxisPainterPrivate::getTickLabelDrawOffset(
6640 const TickLabelData &labelData) const {
6641 /*
6642 calculate label offset from base point at tick (non-trivial, for best visual
6643 appearance): short explanation for bottom axis: The anchor, i.e. the point
6644 in the label that is placed horizontally under the corresponding tick is
6645 always on the label side that is closer to the axis (e.g. the left side of
6646 the text when we're rotating clockwise). On that side, the height is halved
6647 and the resulting point is defined the anchor. This way, a 90 degree rotated
6648 text will be centered under the tick (i.e. displaced horizontally by half
6649 its height). At the same time, a 45 degree rotated text will "point toward"
6650 its tick, as is typical for rotated tick labels.
6651 */
6652 bool doRotation = !qFuzzyIsNull(tickLabelRotation);
6653 bool flip =
6654 qFuzzyCompare(qAbs(tickLabelRotation),
6655 90.0); // perfect +/-90 degree flip. Indicates vertical
6656 // label centering on vertical axes.
6657 double radians = tickLabelRotation / 180.0 * M_PI;
6658 int x = 0, y = 0;
6659 if ((type == QCPAxis::atLeft && tickLabelSide == QCPAxis::lsOutside) ||
6660 (type == QCPAxis::atRight &&
6661 tickLabelSide ==
6662 QCPAxis::lsInside)) // Anchor at right side of tick label
6663 {
6664 if (doRotation) {
6665 if (tickLabelRotation > 0) {
6666 x = -qCos(radians) * labelData.totalBounds.width();
6667 y = flip ? -labelData.totalBounds.width() / 2.0
6668 : -qSin(radians) * labelData.totalBounds.width() -
6669 qCos(radians) * labelData.totalBounds.height() / 2.0;
6670 } else {
6671 x = -qCos(-radians) * labelData.totalBounds.width() -
6672 qSin(-radians) * labelData.totalBounds.height();
6673 y = flip ? +labelData.totalBounds.width() / 2.0
6674 : +qSin(-radians) * labelData.totalBounds.width() -
6675 qCos(-radians) * labelData.totalBounds.height() / 2.0;
6676 }
6677 } else {
6678 x = -labelData.totalBounds.width();
6679 y = -labelData.totalBounds.height() / 2.0;
6680 }
6681 } else if ((type == QCPAxis::atRight &&
6682 tickLabelSide == QCPAxis::lsOutside) ||
6683 (type == QCPAxis::atLeft &&
6684 tickLabelSide ==
6685 QCPAxis::lsInside)) // Anchor at left side of tick label
6686 {
6687 if (doRotation) {
6688 if (tickLabelRotation > 0) {
6689 x = +qSin(radians) * labelData.totalBounds.height();
6690 y = flip ? -labelData.totalBounds.width() / 2.0
6691 : -qCos(radians) * labelData.totalBounds.height() / 2.0;
6692 } else {
6693 x = 0;
6694 y = flip ? +labelData.totalBounds.width() / 2.0
6695 : -qCos(-radians) * labelData.totalBounds.height() / 2.0;
6696 }
6697 } else {
6698 x = 0;
6699 y = -labelData.totalBounds.height() / 2.0;
6700 }
6701 } else if ((type == QCPAxis::atTop && tickLabelSide == QCPAxis::lsOutside) ||
6702 (type == QCPAxis::atBottom &&
6703 tickLabelSide ==
6704 QCPAxis::lsInside)) // Anchor at bottom side of tick label
6705 {
6706 if (doRotation) {
6707 if (tickLabelRotation > 0) {
6708 x = -qCos(radians) * labelData.totalBounds.width() +
6709 qSin(radians) * labelData.totalBounds.height() / 2.0;
6710 y = -qSin(radians) * labelData.totalBounds.width() -
6711 qCos(radians) * labelData.totalBounds.height();
6712 } else {
6713 x = -qSin(-radians) * labelData.totalBounds.height() / 2.0;
6714 y = -qCos(-radians) * labelData.totalBounds.height();
6715 }
6716 } else {
6717 x = -labelData.totalBounds.width() / 2.0;
6718 y = -labelData.totalBounds.height();
6719 }
6720 } else if ((type == QCPAxis::atBottom &&
6721 tickLabelSide == QCPAxis::lsOutside) ||
6722 (type == QCPAxis::atTop &&
6723 tickLabelSide ==
6724 QCPAxis::lsInside)) // Anchor at top side of tick label
6725 {
6726 if (doRotation) {
6727 if (tickLabelRotation > 0) {
6728 x = +qSin(radians) * labelData.totalBounds.height() / 2.0;
6729 y = 0;
6730 } else {
6731 x = -qCos(-radians) * labelData.totalBounds.width() -
6732 qSin(-radians) * labelData.totalBounds.height() / 2.0;
6733 y = +qSin(-radians) * labelData.totalBounds.width();
6734 }
6735 } else {
6736 x = -labelData.totalBounds.width() / 2.0;
6737 y = 0;
6738 }
6739 }
6740
6741 return QPointF(x, y);
6742 }
6743
6744 /*! \internal
6745
6746 Simulates the steps done by \ref placeTickLabel by calculating bounding boxes
6747 of the text label to be drawn, depending on number format etc. Since only the
6748 largest tick label is wanted for the margin calculation, the passed \a
6749 tickLabelsSize is only expanded, if it's currently set to a smaller
6750 width/height.
6751 */
6752 void QCPAxisPainterPrivate::getMaxTickLabelSize(const QFont &font,
6753 const QString &text,
6754 QSize *tickLabelsSize) const {
6755 // note: this function must return the same tick label sizes as the
6756 // placeTickLabel function.
6757 QSize finalSize;
6758 if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) &&
6759 mLabelCache.contains(
6760 text)) // label caching enabled and have cached label
6761 {
6762 const CachedLabel *cachedLabel = mLabelCache.object(text);
6763 finalSize = cachedLabel->pixmap.size();
6764 } else // label caching disabled or no label with this text cached:
6765 {
6766 TickLabelData labelData = getTickLabelData(font, text);
6767 finalSize = labelData.rotatedTotalBounds.size();
6768 }
6769
6770 // expand passed tickLabelsSize if current tick label is larger:
6771 if (finalSize.width() > tickLabelsSize->width())
6772 tickLabelsSize->setWidth(finalSize.width());
6773 if (finalSize.height() > tickLabelsSize->height())
6774 tickLabelsSize->setHeight(finalSize.height());
6775 }
6776
6777 ////////////////////////////////////////////////////////////////////////////////////////////////////
6778 //////////////////// QCPAbstractPlottable
6779 ////////////////////////////////////////////////////////////////////////////////////////////////////
6780
6781 /*! \class QCPAbstractPlottable
6782 \brief The abstract base class for all data representing objects in a plot.
6783
6784 It defines a very basic interface like name, pen, brush, visibility etc. Since
6785 this class is abstract, it can't be instantiated. Use one of the subclasses or
6786 create a subclass yourself to create new ways of displaying data (see
6787 "Creating own plottables" below).
6788
6789 All further specifics are in the subclasses, for example:
6790 \li A normal graph with possibly a line, scatter points and error bars: \ref
6791 QCPGraph (typically created with \ref QCustomPlot::addGraph) \li A parametric
6792 curve: \ref QCPCurve \li A bar chart: \ref QCPBars \li A statistical box plot:
6793 \ref QCPStatisticalBox \li A color encoded two-dimensional map: \ref
6794 QCPColorMap \li An OHLC/Candlestick chart: \ref QCPFinancial
6795
6796 \section plottables-subclassing Creating own plottables
6797
6798 To create an own plottable, you implement a subclass of QCPAbstractPlottable.
6799 These are the pure virtual functions, you must implement: \li \ref clearData
6800 \li \ref selectTest
6801 \li \ref draw
6802 \li \ref drawLegendIcon
6803 \li \ref getKeyRange
6804 \li \ref getValueRange
6805
6806 See the documentation of those functions for what they need to do.
6807
6808 For drawing your plot, you can use the \ref coordsToPixels functions to
6809 translate a point in plot coordinates to pixel coordinates. This function is
6810 quite convenient, because it takes the orientation of the key and value axes
6811 into account for you (x and y are swapped when the key axis is vertical and
6812 the value axis horizontal). If you are worried about performance (i.e. you
6813 need to translate many points in a loop like QCPGraph), you can directly use
6814 \ref QCPAxis::coordToPixel. However, you must then take care about the
6815 orientation of the axis yourself.
6816
6817 Here are some important members you inherit from QCPAbstractPlottable:
6818 <table>
6819 <tr>
6820 <td>QCustomPlot *\b mParentPlot</td>
6821 <td>A pointer to the parent QCustomPlot instance. The parent plot is
6822 inferred from the axes that are passed in the constructor.</td>
6823 </tr><tr>
6824 <td>QString \b mName</td>
6825 <td>The name of the plottable.</td>
6826 </tr><tr>
6827 <td>QPen \b mPen</td>
6828 <td>The generic pen of the plottable. You should use this pen for the most
6829 prominent data representing lines in the plottable (e.g QCPGraph uses this pen
6830 for its graph lines and scatters)</td>
6831 </tr><tr>
6832 <td>QPen \b mSelectedPen</td>
6833 <td>The generic pen that should be used when the plottable is selected
6834 (hint: \ref mainPen gives you the right pen, depending on selection
6835 state).</td>
6836 </tr><tr>
6837 <td>QBrush \b mBrush</td>
6838 <td>The generic brush of the plottable. You should use this brush for the
6839 most prominent fillable structures in the plottable (e.g. QCPGraph uses this
6840 brush to control filling under the graph)</td>
6841 </tr><tr>
6842 <td>QBrush \b mSelectedBrush</td>
6843 <td>The generic brush that should be used when the plottable is selected
6844 (hint: \ref mainBrush gives you the right brush, depending on selection
6845 state).</td>
6846 </tr><tr>
6847 <td>QPointer<QCPAxis>\b mKeyAxis, \b mValueAxis</td>
6848 <td>The key and value axes this plottable is attached to. Call their
6849 QCPAxis::coordToPixel functions to translate coordinates to pixels in either
6850 the key or value dimension. Make sure to check whether the pointer is null
6851 before using it. If one of the axes is null, don't draw the plottable.</td>
6852 </tr><tr>
6853 <td>bool \b mSelected</td>
6854 <td>indicates whether the plottable is selected or not.</td>
6855 </tr>
6856 </table>
6857 */
6858
6859 /* start of documentation of pure virtual functions */
6860
6861 /*! \fn void QCPAbstractPlottable::clearData() = 0
6862 Clears all data in the plottable.
6863 */
6864
6865 /*! \fn void QCPAbstractPlottable::drawLegendIcon(QCPPainter *painter, const
6866 QRect &rect) const = 0 \internal
6867
6868 called by QCPLegend::draw (via QCPPlottableLegendItem::draw) to create a
6869 graphical representation of this plottable inside \a rect, next to the
6870 plottable name.
6871
6872 The passed \a painter has its cliprect set to \a rect, so painting outside of
6873 \a rect won't appear outside the legend icon border.
6874 */
6875
6876 /*! \fn QCPRange QCPAbstractPlottable::getKeyRange(bool &foundRange, SignDomain
6877 inSignDomain) const = 0 \internal
6878
6879 called by rescaleAxes functions to get the full data key bounds. For
6880 logarithmic plots, one can set \a inSignDomain to either \ref sdNegative or
6881 \ref sdPositive in order to restrict the returned range to that sign domain.
6882 E.g. when only negative range is wanted, set \a inSignDomain to \ref
6883 sdNegative and all positive points will be ignored for range calculation. For
6884 no restriction, just set \a inSignDomain to \ref sdBoth (default). \a
6885 foundRange is an output parameter that indicates whether a range could be
6886 found or not. If this is false, you shouldn't use the returned range (e.g. no
6887 points in data).
6888
6889 Note that \a foundRange is not the same as \ref QCPRange::validRange, since
6890 the range returned by this function may have size zero, which wouldn't count
6891 as a valid range.
6892
6893 \see rescaleAxes, getValueRange
6894 */
6895
6896 /*! \fn QCPRange QCPAbstractPlottable::getValueRange(bool &foundRange,
6897 SignDomain inSignDomain) const = 0 \internal
6898
6899 called by rescaleAxes functions to get the full data value bounds. For
6900 logarithmic plots, one can set \a inSignDomain to either \ref sdNegative or
6901 \ref sdPositive in order to restrict the returned range to that sign domain.
6902 E.g. when only negative range is wanted, set \a inSignDomain to \ref
6903 sdNegative and all positive points will be ignored for range calculation. For
6904 no restriction, just set \a inSignDomain to \ref sdBoth (default). \a
6905 foundRange is an output parameter that indicates whether a range could be
6906 found or not. If this is false, you shouldn't use the returned range (e.g. no
6907 points in data).
6908
6909 Note that \a foundRange is not the same as \ref QCPRange::validRange, since
6910 the range returned by this function may have size zero, which wouldn't count
6911 as a valid range.
6912
6913 \see rescaleAxes, getKeyRange
6914 */
6915
6916 /* end of documentation of pure virtual functions */
6917 /* start of documentation of signals */
6918
6919 /*! \fn void QCPAbstractPlottable::selectionChanged(bool selected)
6920
6921 This signal is emitted when the selection state of this plottable has changed,
6922 either by user interaction or by a direct call to \ref setSelected.
6923 */
6924
6925 /*! \fn void QCPAbstractPlottable::selectableChanged(bool selectable);
6926
6927 This signal is emitted when the selectability of this plottable has changed.
6928
6929 \see setSelectable
6930 */
6931
6932 /* end of documentation of signals */
6933
6934 /*!
6935 Constructs an abstract plottable which uses \a keyAxis as its key axis ("x")
6936 and \a valueAxis as its value axis ("y"). \a keyAxis and \a valueAxis must
6937 reside in the same QCustomPlot instance and have perpendicular orientations.
6938 If either of these restrictions is violated, a corresponding message is
6939 printed to the debug output (qDebug), the construction is not aborted, though.
6940
6941 Since QCPAbstractPlottable is an abstract class that defines the basic
6942 interface to plottables, it can't be directly instantiated.
6943
6944 You probably want one of the subclasses like \ref QCPGraph or \ref QCPCurve
6945 instead.
6946 */
6947 QCPAbstractPlottable::QCPAbstractPlottable(QCPAxis *keyAxis, QCPAxis *valueAxis)
6948 : QCPLayerable(keyAxis->parentPlot(), QString(), keyAxis->axisRect()),
6949 mName(),
6950 mAntialiasedFill(true),
6951 mAntialiasedScatters(true),
6952 mAntialiasedErrorBars(false),
6953 mPen(Qt::black),
6954 mSelectedPen(Qt::black),
6955 mBrush(Qt::NoBrush),
6956 mSelectedBrush(Qt::NoBrush),
6957 mKeyAxis(keyAxis),
6958 mValueAxis(valueAxis),
6959 mSelectable(true),
6960 mSelected(false) {
6961 if (keyAxis->parentPlot() != valueAxis->parentPlot())
6962 qDebug() << Q_FUNC_INFO
6963 << "Parent plot of keyAxis is not the same as that of valueAxis.";
6964 if (keyAxis->orientation() == valueAxis->orientation())
6965 qDebug() << Q_FUNC_INFO
6966 << "keyAxis and valueAxis must be orthogonal to each other.";
6967 }
6968
6969 /*!
6970 The name is the textual representation of this plottable as it is displayed
6971 in the legend
6972 (\ref QCPLegend). It may contain any UTF-8 characters, including newlines.
6973 */
6974 void QCPAbstractPlottable::setName(const QString &name) { mName = name; }
6975
6976 /*!
6977 Sets whether fills of this plottable are drawn antialiased or not.
6978
6979 Note that this setting may be overridden by \ref
6980 QCustomPlot::setAntialiasedElements and \ref
6981 QCustomPlot::setNotAntialiasedElements.
6982 */
6983 void QCPAbstractPlottable::setAntialiasedFill(bool enabled) {
6984 mAntialiasedFill = enabled;
6985 }
6986
6987 /*!
6988 Sets whether the scatter symbols of this plottable are drawn antialiased or
6989 not.
6990
6991 Note that this setting may be overridden by \ref
6992 QCustomPlot::setAntialiasedElements and \ref
6993 QCustomPlot::setNotAntialiasedElements.
6994 */
6995 void QCPAbstractPlottable::setAntialiasedScatters(bool enabled) {
6996 mAntialiasedScatters = enabled;
6997 }
6998
6999 /*!
7000 Sets whether the error bars of this plottable are drawn antialiased or not.
7001
7002 Note that this setting may be overridden by \ref
7003 QCustomPlot::setAntialiasedElements and \ref
7004 QCustomPlot::setNotAntialiasedElements.
7005 */
7006 void QCPAbstractPlottable::setAntialiasedErrorBars(bool enabled) {
7007 mAntialiasedErrorBars = enabled;
7008 }
7009
7010 /*!
7011 The pen is used to draw basic lines that make up the plottable representation
7012 in the plot.
7013
7014 For example, the \ref QCPGraph subclass draws its graph lines with this pen.
7015
7016 \see setBrush
7017 */
7018 void QCPAbstractPlottable::setPen(const QPen &pen) { mPen = pen; }
7019
7020 /*!
7021 When the plottable is selected, this pen is used to draw basic lines instead
7022 of the normal pen set via \ref setPen.
7023
7024 \see setSelected, setSelectable, setSelectedBrush, selectTest
7025 */
7026 void QCPAbstractPlottable::setSelectedPen(const QPen &pen) {
7027 mSelectedPen = pen;
7028 }
7029
7030 /*!
7031 The brush is used to draw basic fills of the plottable representation in the
7032 plot. The Fill can be a color, gradient or texture, see the usage of QBrush.
7033
7034 For example, the \ref QCPGraph subclass draws the fill under the graph with
7035 this brush, when it's not set to Qt::NoBrush.
7036
7037 \see setPen
7038 */
7039 void QCPAbstractPlottable::setBrush(const QBrush &brush) { mBrush = brush; }
7040
7041 /*!
7042 When the plottable is selected, this brush is used to draw fills instead of
7043 the normal brush set via \ref setBrush.
7044
7045 \see setSelected, setSelectable, setSelectedPen, selectTest
7046 */
7047 void QCPAbstractPlottable::setSelectedBrush(const QBrush &brush) {
7048 mSelectedBrush = brush;
7049 }
7050
7051 /*!
7052 The key axis of a plottable can be set to any axis of a QCustomPlot, as long
7053 as it is orthogonal to the plottable's value axis. This function performs no
7054 checks to make sure this is the case. The typical mathematical choice is to
7055 use the x-axis (QCustomPlot::xAxis) as key axis and the y-axis
7056 (QCustomPlot::yAxis) as value axis.
7057
7058 Normally, the key and value axes are set in the constructor of the plottable
7059 (or \ref QCustomPlot::addGraph when working with QCPGraphs through the
7060 dedicated graph interface).
7061
7062 \see setValueAxis
7063 */
7064 void QCPAbstractPlottable::setKeyAxis(QCPAxis *axis) { mKeyAxis = axis; }
7065
7066 /*!
7067 The value axis of a plottable can be set to any axis of a QCustomPlot, as long
7068 as it is orthogonal to the plottable's key axis. This function performs no
7069 checks to make sure this is the case. The typical mathematical choice is to
7070 use the x-axis (QCustomPlot::xAxis) as key axis and the y-axis
7071 (QCustomPlot::yAxis) as value axis.
7072
7073 Normally, the key and value axes are set in the constructor of the plottable
7074 (or \ref QCustomPlot::addGraph when working with QCPGraphs through the
7075 dedicated graph interface).
7076
7077 \see setKeyAxis
7078 */
7079 void QCPAbstractPlottable::setValueAxis(QCPAxis *axis) { mValueAxis = axis; }
7080
7081 /*!
7082 Sets whether the user can (de-)select this plottable by clicking on the
7083 QCustomPlot surface. (When \ref QCustomPlot::setInteractions contains
7084 iSelectPlottables.)
7085
7086 However, even when \a selectable was set to false, it is possible to set the
7087 selection manually, by calling \ref setSelected directly.
7088
7089 \see setSelected
7090 */
7091 void QCPAbstractPlottable::setSelectable(bool selectable) {
7092 if (mSelectable != selectable) {
7093 mSelectable = selectable;
7094 emit selectableChanged(mSelectable);
7095 }
7096 }
7097
7098 /*!
7099 Sets whether this plottable is selected or not. When selected, it uses a
7100 different pen and brush to draw its lines and fills, see \ref setSelectedPen
7101 and \ref setSelectedBrush.
7102
7103 The entire selection mechanism for plottables is handled automatically when
7104 \ref QCustomPlot::setInteractions contains iSelectPlottables. You only need to
7105 call this function when you wish to change the selection state manually.
7106
7107 This function can change the selection state even when \ref setSelectable was
7108 set to false.
7109
7110 emits the \ref selectionChanged signal when \a selected is different from the
7111 previous selection state.
7112
7113 \see setSelectable, selectTest
7114 */
7115 void QCPAbstractPlottable::setSelected(bool selected) {
7116 if (mSelected != selected) {
7117 mSelected = selected;
7118 emit selectionChanged(mSelected);
7119 }
7120 }
7121
7122 /*!
7123 Rescales the key and value axes associated with this plottable to contain all
7124 displayed data, so the whole plottable is visible. If the scaling of an axis
7125 is logarithmic, rescaleAxes will make sure not to rescale to an illegal range
7126 i.e. a range containing different signs and/or zero. Instead it will stay in
7127 the current sign domain and ignore all parts of the plottable that lie outside
7128 of that domain.
7129
7130 \a onlyEnlarge makes sure the ranges are only expanded, never reduced. So it's
7131 possible to show multiple plottables in their entirety by multiple calls to
7132 rescaleAxes where the first call has \a onlyEnlarge set to false (the
7133 default), and all subsequent set to true.
7134
7135 \see rescaleKeyAxis, rescaleValueAxis, QCustomPlot::rescaleAxes,
7136 QCPAxis::rescale
7137 */
7138 void QCPAbstractPlottable::rescaleAxes(bool onlyEnlarge) const {
7139 rescaleKeyAxis(onlyEnlarge);
7140 rescaleValueAxis(onlyEnlarge);
7141 }
7142
7143 /*!
7144 Rescales the key axis of the plottable so the whole plottable is visible.
7145
7146 See \ref rescaleAxes for detailed behaviour.
7147 */
7148 void QCPAbstractPlottable::rescaleKeyAxis(bool onlyEnlarge) const {
7149 QCPAxis *keyAxis = mKeyAxis.data();
7150 if (!keyAxis) {
7151 qDebug() << Q_FUNC_INFO << "invalid key axis";
7152 return;
7153 }
7154
7155 SignDomain signDomain = sdBoth;
7156 if (keyAxis->scaleType() == QCPAxis::stLogarithmic)
7157 signDomain = (keyAxis->range().upper < 0 ? sdNegative : sdPositive);
7158
7159 bool foundRange;
7160 QCPRange newRange = getKeyRange(foundRange, signDomain);
7161 if (foundRange) {
7162 if (onlyEnlarge) newRange.expand(keyAxis->range());
7163 if (!QCPRange::validRange(
7164 newRange)) // likely due to range being zero (plottable has only
7165 // constant data in this axis dimension), shift current
7166 // range to at least center the plottable
7167 {
7168 double center =
7169 (newRange.lower + newRange.upper) *
7170 0.5; // upper and lower should be equal anyway, but just to make
7171 // sure, incase validRange returned false for other reason
7172 if (keyAxis->scaleType() == QCPAxis::stLinear) {
7173 newRange.lower = center - keyAxis->range().size() / 2.0;
7174 newRange.upper = center + keyAxis->range().size() / 2.0;
7175 } else // scaleType() == stLogarithmic
7176 {
7177 newRange.lower =
7178 center / qSqrt(keyAxis->range().upper / keyAxis->range().lower);
7179 newRange.upper =
7180 center * qSqrt(keyAxis->range().upper / keyAxis->range().lower);
7181 }
7182 }
7183 keyAxis->setRange(newRange);
7184 }
7185 }
7186
7187 /*!
7188 Rescales the value axis of the plottable so the whole plottable is visible.
7189
7190 Returns true if the axis was actually scaled. This might not be the case if
7191 this plottable has an invalid range, e.g. because it has no data points.
7192
7193 See \ref rescaleAxes for detailed behaviour.
7194 */
7195 void QCPAbstractPlottable::rescaleValueAxis(bool onlyEnlarge) const {
7196 QCPAxis *valueAxis = mValueAxis.data();
7197 if (!valueAxis) {
7198 qDebug() << Q_FUNC_INFO << "invalid value axis";
7199 return;
7200 }
7201
7202 SignDomain signDomain = sdBoth;
7203 if (valueAxis->scaleType() == QCPAxis::stLogarithmic)
7204 signDomain = (valueAxis->range().upper < 0 ? sdNegative : sdPositive);
7205
7206 bool foundRange;
7207 QCPRange newRange = getValueRange(foundRange, signDomain);
7208 if (foundRange) {
7209 if (onlyEnlarge) newRange.expand(valueAxis->range());
7210 if (!QCPRange::validRange(
7211 newRange)) // likely due to range being zero (plottable has only
7212 // constant data in this axis dimension), shift current
7213 // range to at least center the plottable
7214 {
7215 double center =
7216 (newRange.lower + newRange.upper) *
7217 0.5; // upper and lower should be equal anyway, but just to make
7218 // sure, incase validRange returned false for other reason
7219 if (valueAxis->scaleType() == QCPAxis::stLinear) {
7220 newRange.lower = center - valueAxis->range().size() / 2.0;
7221 newRange.upper = center + valueAxis->range().size() / 2.0;
7222 } else // scaleType() == stLogarithmic
7223 {
7224 newRange.lower =
7225 center / qSqrt(valueAxis->range().upper / valueAxis->range().lower);
7226 newRange.upper =
7227 center * qSqrt(valueAxis->range().upper / valueAxis->range().lower);
7228 }
7229 }
7230 valueAxis->setRange(newRange);
7231 }
7232 }
7233
7234 /*!
7235 Adds this plottable to the legend of the parent QCustomPlot
7236 (QCustomPlot::legend).
7237
7238 Normally, a QCPPlottableLegendItem is created and inserted into the legend. If
7239 the plottable needs a more specialized representation in the legend, this
7240 function will take this into account and instead create the specialized
7241 subclass of QCPAbstractLegendItem.
7242
7243 Returns true on success, i.e. when the legend exists and a legend item
7244 associated with this plottable isn't already in the legend.
7245
7246 \see removeFromLegend, QCPLegend::addItem
7247 */
7248 bool QCPAbstractPlottable::addToLegend() {
7249 if (!mParentPlot || !mParentPlot->legend) return false;
7250
7251 if (!mParentPlot->legend->hasItemWithPlottable(this)) {
7252 mParentPlot->legend->addItem(
7253 new QCPPlottableLegendItem(mParentPlot->legend, this));
7254 return true;
7255 } else
7256 return false;
7257 }
7258
7259 /*!
7260 Removes the plottable from the legend of the parent QCustomPlot. This means
7261 the QCPAbstractLegendItem (usually a QCPPlottableLegendItem) that is
7262 associated with this plottable is removed.
7263
7264 Returns true on success, i.e. if the legend exists and a legend item
7265 associated with this plottable was found and removed.
7266
7267 \see addToLegend, QCPLegend::removeItem
7268 */
7269 bool QCPAbstractPlottable::removeFromLegend() const {
7270 if (!mParentPlot->legend) return false;
7271
7272 if (QCPPlottableLegendItem *lip =
7273 mParentPlot->legend->itemWithPlottable(this))
7274 return mParentPlot->legend->removeItem(lip);
7275 else
7276 return false;
7277 }
7278
7279 /* inherits documentation from base class */
7280 QRect QCPAbstractPlottable::clipRect() const {
7281 if (mKeyAxis && mValueAxis)
7282 return mKeyAxis.data()->axisRect()->rect() &
7283 mValueAxis.data()->axisRect()->rect();
7284 else
7285 return QRect();
7286 }
7287
7288 /* inherits documentation from base class */
7289 QCP::Interaction QCPAbstractPlottable::selectionCategory() const {
7290 return QCP::iSelectPlottables;
7291 }
7292
7293 /*! \internal
7294
7295 Convenience function for transforming a key/value pair to pixels on the
7296 QCustomPlot surface, taking the orientations of the axes associated with this
7297 plottable into account (e.g. whether key represents x or y).
7298
7299 \a key and \a value are transformed to the coodinates in pixels and are
7300 written to \a x and \a y.
7301
7302 \see pixelsToCoords, QCPAxis::coordToPixel
7303 */
7304 void QCPAbstractPlottable::coordsToPixels(double key, double value, double &x,
7305 double &y) const {
7306 QCPAxis *keyAxis = mKeyAxis.data();
7307 QCPAxis *valueAxis = mValueAxis.data();
7308 if (!keyAxis || !valueAxis) {
7309 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
7310 return;
7311 }
7312
7313 if (keyAxis->orientation() == Qt::Horizontal) {
7314 x = keyAxis->coordToPixel(key);
7315 y = valueAxis->coordToPixel(value);
7316 } else {
7317 y = keyAxis->coordToPixel(key);
7318 x = valueAxis->coordToPixel(value);
7319 }
7320 }
7321
7322 /*! \internal
7323 \overload
7324
7325 Returns the input as pixel coordinates in a QPointF.
7326 */
7327 const QPointF QCPAbstractPlottable::coordsToPixels(double key,
7328 double value) const {
7329 QCPAxis *keyAxis = mKeyAxis.data();
7330 QCPAxis *valueAxis = mValueAxis.data();
7331 if (!keyAxis || !valueAxis) {
7332 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
7333 return QPointF();
7334 }
7335
7336 if (keyAxis->orientation() == Qt::Horizontal)
7337 return QPointF(keyAxis->coordToPixel(key), valueAxis->coordToPixel(value));
7338 else
7339 return QPointF(valueAxis->coordToPixel(value), keyAxis->coordToPixel(key));
7340 }
7341
7342 /*! \internal
7343
7344 Convenience function for transforming a x/y pixel pair on the QCustomPlot
7345 surface to plot coordinates, taking the orientations of the axes associated
7346 with this plottable into account (e.g. whether key represents x or y).
7347
7348 \a x and \a y are transformed to the plot coodinates and are written to \a key
7349 and \a value.
7350
7351 \see coordsToPixels, QCPAxis::coordToPixel
7352 */
7353 void QCPAbstractPlottable::pixelsToCoords(double x, double y, double &key,
7354 double &value) const {
7355 QCPAxis *keyAxis = mKeyAxis.data();
7356 QCPAxis *valueAxis = mValueAxis.data();
7357 if (!keyAxis || !valueAxis) {
7358 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
7359 return;
7360 }
7361
7362 if (keyAxis->orientation() == Qt::Horizontal) {
7363 key = keyAxis->pixelToCoord(x);
7364 value = valueAxis->pixelToCoord(y);
7365 } else {
7366 key = keyAxis->pixelToCoord(y);
7367 value = valueAxis->pixelToCoord(x);
7368 }
7369 }
7370
7371 /*! \internal
7372 \overload
7373
7374 Returns the pixel input \a pixelPos as plot coordinates \a key and \a value.
7375 */
7376 void QCPAbstractPlottable::pixelsToCoords(const QPointF &pixelPos, double &key,
7377 double &value) const {
7378 pixelsToCoords(pixelPos.x(), pixelPos.y(), key, value);
7379 }
7380
7381 /*! \internal
7382
7383 Returns the pen that should be used for drawing lines of the plottable.
7384 Returns mPen when the graph is not selected and mSelectedPen when it is.
7385 */
7386 QPen QCPAbstractPlottable::mainPen() const {
7387 return mSelected ? mSelectedPen : mPen;
7388 }
7389
7390 /*! \internal
7391
7392 Returns the brush that should be used for drawing fills of the plottable.
7393 Returns mBrush when the graph is not selected and mSelectedBrush when it is.
7394 */
7395 QBrush QCPAbstractPlottable::mainBrush() const {
7396 return mSelected ? mSelectedBrush : mBrush;
7397 }
7398
7399 /*! \internal
7400
7401 A convenience function to easily set the QPainter::Antialiased hint on the
7402 provided \a painter before drawing plottable lines.
7403
7404 This is the antialiasing state the painter passed to the \ref draw method is
7405 in by default.
7406
7407 This function takes into account the local setting of the antialiasing flag as
7408 well as the overrides set with \ref QCustomPlot::setAntialiasedElements and
7409 \ref QCustomPlot::setNotAntialiasedElements.
7410
7411 \see setAntialiased, applyFillAntialiasingHint, applyScattersAntialiasingHint,
7412 applyErrorBarsAntialiasingHint
7413 */
7414 void QCPAbstractPlottable::applyDefaultAntialiasingHint(
7415 QCPPainter *painter) const {
7416 applyAntialiasingHint(painter, mAntialiased, QCP::aePlottables);
7417 }
7418
7419 /*! \internal
7420
7421 A convenience function to easily set the QPainter::Antialiased hint on the
7422 provided \a painter before drawing plottable fills.
7423
7424 This function takes into account the local setting of the antialiasing flag as
7425 well as the overrides set with \ref QCustomPlot::setAntialiasedElements and
7426 \ref QCustomPlot::setNotAntialiasedElements.
7427
7428 \see setAntialiased, applyDefaultAntialiasingHint,
7429 applyScattersAntialiasingHint, applyErrorBarsAntialiasingHint
7430 */
7431 void QCPAbstractPlottable::applyFillAntialiasingHint(
7432 QCPPainter *painter) const {
7433 applyAntialiasingHint(painter, mAntialiasedFill, QCP::aeFills);
7434 }
7435
7436 /*! \internal
7437
7438 A convenience function to easily set the QPainter::Antialiased hint on the
7439 provided \a painter before drawing plottable scatter points.
7440
7441 This function takes into account the local setting of the antialiasing flag as
7442 well as the overrides set with \ref QCustomPlot::setAntialiasedElements and
7443 \ref QCustomPlot::setNotAntialiasedElements.
7444
7445 \see setAntialiased, applyFillAntialiasingHint, applyDefaultAntialiasingHint,
7446 applyErrorBarsAntialiasingHint
7447 */
7448 void QCPAbstractPlottable::applyScattersAntialiasingHint(
7449 QCPPainter *painter) const {
7450 applyAntialiasingHint(painter, mAntialiasedScatters, QCP::aeScatters);
7451 }
7452
7453 /*! \internal
7454
7455 A convenience function to easily set the QPainter::Antialiased hint on the
7456 provided \a painter before drawing plottable error bars.
7457
7458 This function takes into account the local setting of the antialiasing flag as
7459 well as the overrides set with \ref QCustomPlot::setAntialiasedElements and
7460 \ref QCustomPlot::setNotAntialiasedElements.
7461
7462 \see setAntialiased, applyFillAntialiasingHint, applyScattersAntialiasingHint,
7463 applyDefaultAntialiasingHint
7464 */
7465 void QCPAbstractPlottable::applyErrorBarsAntialiasingHint(
7466 QCPPainter *painter) const {
7467 applyAntialiasingHint(painter, mAntialiasedErrorBars, QCP::aeErrorBars);
7468 }
7469
7470 /*! \internal
7471
7472 Finds the shortest squared distance of \a point to the line segment defined by
7473 \a start and \a end.
7474
7475 This function may be used to help with the implementation of the \ref
7476 selectTest function for specific plottables.
7477
7478 \note This function is identical to QCPAbstractItem::distSqrToLine
7479 */
7480 double QCPAbstractPlottable::distSqrToLine(const QPointF &start,
7481 const QPointF &end,
7482 const QPointF &point) const {
7483 QVector2D a(start);
7484 QVector2D b(end);
7485 QVector2D p(point);
7486 QVector2D v(b - a);
7487
7488 double vLengthSqr = v.lengthSquared();
7489 if (!qFuzzyIsNull(vLengthSqr)) {
7490 double mu = QVector2D::dotProduct(p - a, v) / vLengthSqr;
7491 if (mu < 0)
7492 return (a - p).lengthSquared();
7493 else if (mu > 1)
7494 return (b - p).lengthSquared();
7495 else
7496 return ((a + mu * v) - p).lengthSquared();
7497 } else
7498 return (a - p).lengthSquared();
7499 }
7500
7501 /* inherits documentation from base class */
7502 void QCPAbstractPlottable::selectEvent(QMouseEvent *event, bool additive,
7503 const QVariant &details,
7504 bool *selectionStateChanged) {
7505 Q_UNUSED(event)
7506 Q_UNUSED(details)
7507 if (mSelectable) {
7508 bool selBefore = mSelected;
7509 setSelected(additive ? !mSelected : true);
7510 if (selectionStateChanged) *selectionStateChanged = mSelected != selBefore;
7511 }
7512 }
7513
7514 /* inherits documentation from base class */
7515 void QCPAbstractPlottable::deselectEvent(bool *selectionStateChanged) {
7516 if (mSelectable) {
7517 bool selBefore = mSelected;
7518 setSelected(false);
7519 if (selectionStateChanged) *selectionStateChanged = mSelected != selBefore;
7520 }
7521 }
7522
7523 ////////////////////////////////////////////////////////////////////////////////////////////////////
7524 //////////////////// QCPItemAnchor
7525 ////////////////////////////////////////////////////////////////////////////////////////////////////
7526
7527 /*! \class QCPItemAnchor
7528 \brief An anchor of an item to which positions can be attached to.
7529
7530 An item (QCPAbstractItem) may have one or more anchors. Unlike
7531 QCPItemPosition, an anchor doesn't control anything on its item, but provides
7532 a way to tie other items via their positions to the anchor.
7533
7534 For example, a QCPItemRect is defined by its positions \a topLeft and \a
7535 bottomRight. Additionally it has various anchors like \a top, \a topRight or
7536 \a bottomLeft etc. So you can attach the \a start (which is a QCPItemPosition)
7537 of a QCPItemLine to one of the anchors by calling
7538 QCPItemPosition::setParentAnchor on \a start, passing the wanted anchor of the
7539 QCPItemRect. This way the start of the line will now always follow the
7540 respective anchor location on the rect item.
7541
7542 Note that QCPItemPosition derives from QCPItemAnchor, so every position can
7543 also serve as an anchor to other positions.
7544
7545 To learn how to provide anchors in your own item subclasses, see the
7546 subclassing section of the QCPAbstractItem documentation.
7547 */
7548
7549 /* start documentation of inline functions */
7550
7551 /*! \fn virtual QCPItemPosition *QCPItemAnchor::toQCPItemPosition()
7552
7553 Returns 0 if this instance is merely a QCPItemAnchor, and a valid pointer of
7554 type QCPItemPosition* if it actually is a QCPItemPosition (which is a subclass
7555 of QCPItemAnchor).
7556
7557 This safe downcast functionality could also be achieved with a dynamic_cast.
7558 However, QCustomPlot avoids dynamic_cast to work with projects that don't have
7559 RTTI support enabled (e.g. -fno-rtti flag with gcc compiler).
7560 */
7561
7562 /* end documentation of inline functions */
7563
7564 /*!
7565 Creates a new QCPItemAnchor. You shouldn't create QCPItemAnchor instances
7566 directly, even if you want to make a new item subclass. Use \ref
7567 QCPAbstractItem::createAnchor instead, as explained in the subclassing section
7568 of the QCPAbstractItem documentation.
7569 */
7570 QCPItemAnchor::QCPItemAnchor(QCustomPlot *parentPlot,
7571 QCPAbstractItem *parentItem, const QString name,
7572 int anchorId)
7573 : mName(name),
7574 mParentPlot(parentPlot),
7575 mParentItem(parentItem),
7576 mAnchorId(anchorId) {}
7577
7578 QCPItemAnchor::~QCPItemAnchor() {
7579 // unregister as parent at children:
7580 foreach (QCPItemPosition *child, mChildrenX.toList()) {
7581 if (child->parentAnchorX() == this)
7582 child->setParentAnchorX(0); // this acts back on this anchor and child
7583 // removes itself from mChildrenX
7584 }
7585 foreach (QCPItemPosition *child, mChildrenY.toList()) {
7586 if (child->parentAnchorY() == this)
7587 child->setParentAnchorY(0); // this acts back on this anchor and child
7588 // removes itself from mChildrenY
7589 }
7590 }
7591
7592 /*!
7593 Returns the final absolute pixel position of the QCPItemAnchor on the
7594 QCustomPlot surface.
7595
7596 The pixel information is internally retrieved via
7597 QCPAbstractItem::anchorPixelPosition of the parent item, QCPItemAnchor is just
7598 an intermediary.
7599 */
7600 QPointF QCPItemAnchor::pixelPoint() const {
7601 if (mParentItem) {
7602 if (mAnchorId > -1) {
7603 return mParentItem->anchorPixelPoint(mAnchorId);
7604 } else {
7605 qDebug() << Q_FUNC_INFO << "no valid anchor id set:" << mAnchorId;
7606 return QPointF();
7607 }
7608 } else {
7609 qDebug() << Q_FUNC_INFO << "no parent item set";
7610 return QPointF();
7611 }
7612 }
7613
7614 /*! \internal
7615
7616 Adds \a pos to the childX list of this anchor, which keeps track of which
7617 children use this anchor as parent anchor for the respective coordinate. This
7618 is necessary to notify the children prior to destruction of the anchor.
7619
7620 Note that this function does not change the parent setting in \a pos.
7621 */
7622 void QCPItemAnchor::addChildX(QCPItemPosition *pos) {
7623 if (!mChildrenX.contains(pos))
7624 mChildrenX.insert(pos);
7625 else
7626 qDebug() << Q_FUNC_INFO << "provided pos is child already"
7627 << reinterpret_cast<quintptr>(pos);
7628 }
7629
7630 /*! \internal
7631
7632 Removes \a pos from the childX list of this anchor.
7633
7634 Note that this function does not change the parent setting in \a pos.
7635 */
7636 void QCPItemAnchor::removeChildX(QCPItemPosition *pos) {
7637 if (!mChildrenX.remove(pos))
7638 qDebug() << Q_FUNC_INFO << "provided pos isn't child"
7639 << reinterpret_cast<quintptr>(pos);
7640 }
7641
7642 /*! \internal
7643
7644 Adds \a pos to the childY list of this anchor, which keeps track of which
7645 children use this anchor as parent anchor for the respective coordinate. This
7646 is necessary to notify the children prior to destruction of the anchor.
7647
7648 Note that this function does not change the parent setting in \a pos.
7649 */
7650 void QCPItemAnchor::addChildY(QCPItemPosition *pos) {
7651 if (!mChildrenY.contains(pos))
7652 mChildrenY.insert(pos);
7653 else
7654 qDebug() << Q_FUNC_INFO << "provided pos is child already"
7655 << reinterpret_cast<quintptr>(pos);
7656 }
7657
7658 /*! \internal
7659
7660 Removes \a pos from the childY list of this anchor.
7661
7662 Note that this function does not change the parent setting in \a pos.
7663 */
7664 void QCPItemAnchor::removeChildY(QCPItemPosition *pos) {
7665 if (!mChildrenY.remove(pos))
7666 qDebug() << Q_FUNC_INFO << "provided pos isn't child"
7667 << reinterpret_cast<quintptr>(pos);
7668 }
7669
7670 ////////////////////////////////////////////////////////////////////////////////////////////////////
7671 //////////////////// QCPItemPosition
7672 ////////////////////////////////////////////////////////////////////////////////////////////////////
7673
7674 /*! \class QCPItemPosition
7675 \brief Manages the position of an item.
7676
7677 Every item has at least one public QCPItemPosition member pointer which
7678 provides ways to position the item on the QCustomPlot surface. Some items have
7679 multiple positions, for example QCPItemRect has two: \a topLeft and \a
7680 bottomRight.
7681
7682 QCPItemPosition has a type (\ref PositionType) that can be set with \ref
7683 setType. This type defines how coordinates passed to \ref setCoords are to be
7684 interpreted, e.g. as absolute pixel coordinates, as plot coordinates of
7685 certain axes, etc. For more advanced plots it is also possible to assign
7686 different types per X/Y coordinate of the position (see \ref setTypeX, \ref
7687 setTypeY). This way an item could be positioned at a fixed pixel distance from
7688 the top in the Y direction, while following a plot coordinate in the X
7689 direction.
7690
7691 A QCPItemPosition may have a parent QCPItemAnchor, see \ref setParentAnchor.
7692 This way you can tie multiple items together. If the QCPItemPosition has a
7693 parent, its coordinates (\ref setCoords) are considered to be absolute pixels
7694 in the reference frame of the parent anchor, where (0, 0) means directly ontop
7695 of the parent anchor. For example, You could attach the \a start position of
7696 a QCPItemLine to the \a bottom anchor of a QCPItemText to make the starting
7697 point of the line always be centered under the text label, no matter where the
7698 text is moved to. For more advanced plots, it is possible to assign different
7699 parent anchors per X/Y coordinate of the position, see \ref setParentAnchorX,
7700 \ref setParentAnchorY. This way an item could follow another item in the X
7701 direction but stay at a fixed position in the Y direction. Or even follow item
7702 A in X, and item B in Y.
7703
7704 Note that every QCPItemPosition inherits from QCPItemAnchor and thus can
7705 itself be used as parent anchor for other positions.
7706
7707 To set the apparent pixel position on the QCustomPlot surface directly, use
7708 \ref setPixelPoint. This works no matter what type this QCPItemPosition is or
7709 what parent-child situation it is in, as \ref setPixelPoint transforms the
7710 coordinates appropriately, to make the position appear at the specified pixel
7711 values.
7712 */
7713
7714 /* start documentation of inline functions */
7715
7716 /*! \fn QCPItemPosition::PositionType *QCPItemPosition::type() const
7717
7718 Returns the current position type.
7719
7720 If different types were set for X and Y (\ref setTypeX, \ref setTypeY), this
7721 method returns the type of the X coordinate. In that case rather use \a
7722 typeX() and \a typeY().
7723
7724 \see setType
7725 */
7726
7727 /*! \fn QCPItemAnchor *QCPItemPosition::parentAnchor() const
7728
7729 Returns the current parent anchor.
7730
7731 If different parent anchors were set for X and Y (\ref setParentAnchorX, \ref
7732 setParentAnchorY), this method returns the parent anchor of the Y coordinate.
7733 In that case rather use \a parentAnchorX() and \a parentAnchorY().
7734
7735 \see setParentAnchor
7736 */
7737
7738 /* end documentation of inline functions */
7739
7740 /*!
7741 Creates a new QCPItemPosition. You shouldn't create QCPItemPosition instances
7742 directly, even if you want to make a new item subclass. Use \ref
7743 QCPAbstractItem::createPosition instead, as explained in the subclassing
7744 section of the QCPAbstractItem documentation.
7745 */
7746 QCPItemPosition::QCPItemPosition(QCustomPlot *parentPlot,
7747 QCPAbstractItem *parentItem,
7748 const QString name)
7749 : QCPItemAnchor(parentPlot, parentItem, name),
7750 mPositionTypeX(ptAbsolute),
7751 mPositionTypeY(ptAbsolute),
7752 mKey(0),
7753 mValue(0),
7754 mParentAnchorX(0),
7755 mParentAnchorY(0) {}
7756
7757 QCPItemPosition::~QCPItemPosition() {
7758 // unregister as parent at children:
7759 // Note: this is done in ~QCPItemAnchor again, but it's important
7760 // QCPItemPosition does it itself, because only then
7761 // the setParentAnchor(0) call the correct QCPItemPosition::pixelPoint
7762 // function instead of QCPItemAnchor::pixelPoint
7763 foreach (QCPItemPosition *child, mChildrenX.toList()) {
7764 if (child->parentAnchorX() == this)
7765 child->setParentAnchorX(0); // this acts back on this anchor and child
7766 // removes itself from mChildrenX
7767 }
7768 foreach (QCPItemPosition *child, mChildrenY.toList()) {
7769 if (child->parentAnchorY() == this)
7770 child->setParentAnchorY(0); // this acts back on this anchor and child
7771 // removes itself from mChildrenY
7772 }
7773 // unregister as child in parent:
7774 if (mParentAnchorX) mParentAnchorX->removeChildX(this);
7775 if (mParentAnchorY) mParentAnchorY->removeChildY(this);
7776 }
7777
7778 /* can't make this a header inline function, because QPointer breaks with
7779 * forward declared types, see QTBUG-29588 */
7780 QCPAxisRect *QCPItemPosition::axisRect() const { return mAxisRect.data(); }
7781
7782 /*!
7783 Sets the type of the position. The type defines how the coordinates passed to
7784 \ref setCoords should be handled and how the QCPItemPosition should behave in
7785 the plot.
7786
7787 The possible values for \a type can be separated in two main categories:
7788
7789 \li The position is regarded as a point in plot coordinates. This corresponds
7790 to \ref ptPlotCoords and requires two axes that define the plot coordinate
7791 system. They can be specified with \ref setAxes. By default, the QCustomPlot's
7792 x- and yAxis are used.
7793
7794 \li The position is fixed on the QCustomPlot surface, i.e. independent of axis
7795 ranges. This corresponds to all other types, i.e. \ref ptAbsolute, \ref
7796 ptViewportRatio and \ref ptAxisRectRatio. They differ only in the way the
7797 absolute position is described, see the documentation of \ref PositionType for
7798 details. For \ref ptAxisRectRatio, note that you can specify the axis rect
7799 with \ref setAxisRect. By default this is set to the main axis rect.
7800
7801 Note that the position type \ref ptPlotCoords is only available (and sensible)
7802 when the position has no parent anchor (\ref setParentAnchor).
7803
7804 If the type is changed, the apparent pixel position on the plot is preserved.
7805 This means the coordinates as retrieved with coords() and set with \ref
7806 setCoords may change in the process.
7807
7808 This method sets the type for both X and Y directions. It is also possible to
7809 set different types for X and Y, see \ref setTypeX, \ref setTypeY.
7810 */
7811 void QCPItemPosition::setType(QCPItemPosition::PositionType type) {
7812 setTypeX(type);
7813 setTypeY(type);
7814 }
7815
7816 /*!
7817 This method sets the position type of the X coordinate to \a type.
7818
7819 For a detailed description of what a position type is, see the documentation
7820 of \ref setType.
7821
7822 \see setType, setTypeY
7823 */
7824 void QCPItemPosition::setTypeX(QCPItemPosition::PositionType type) {
7825 if (mPositionTypeX != type) {
7826 // if switching from or to coordinate type that isn't valid (e.g. because
7827 // axes or axis rect were deleted), don't try to recover the pixelPoint()
7828 // because it would output a qDebug warning.
7829 bool retainPixelPosition = true;
7830 if ((mPositionTypeX == ptPlotCoords || type == ptPlotCoords) &&
7831 (!mKeyAxis || !mValueAxis))
7832 retainPixelPosition = false;
7833 if ((mPositionTypeX == ptAxisRectRatio || type == ptAxisRectRatio) &&
7834 (!mAxisRect))
7835 retainPixelPosition = false;
7836
7837 QPointF pixel;
7838 if (retainPixelPosition) pixel = pixelPoint();
7839
7840 mPositionTypeX = type;
7841
7842 if (retainPixelPosition) setPixelPoint(pixel);
7843 }
7844 }
7845
7846 /*!
7847 This method sets the position type of the Y coordinate to \a type.
7848
7849 For a detailed description of what a position type is, see the documentation
7850 of \ref setType.
7851
7852 \see setType, setTypeX
7853 */
7854 void QCPItemPosition::setTypeY(QCPItemPosition::PositionType type) {
7855 if (mPositionTypeY != type) {
7856 // if switching from or to coordinate type that isn't valid (e.g. because
7857 // axes or axis rect were deleted), don't try to recover the pixelPoint()
7858 // because it would output a qDebug warning.
7859 bool retainPixelPosition = true;
7860 if ((mPositionTypeY == ptPlotCoords || type == ptPlotCoords) &&
7861 (!mKeyAxis || !mValueAxis))
7862 retainPixelPosition = false;
7863 if ((mPositionTypeY == ptAxisRectRatio || type == ptAxisRectRatio) &&
7864 (!mAxisRect))
7865 retainPixelPosition = false;
7866
7867 QPointF pixel;
7868 if (retainPixelPosition) pixel = pixelPoint();
7869
7870 mPositionTypeY = type;
7871
7872 if (retainPixelPosition) setPixelPoint(pixel);
7873 }
7874 }
7875
7876 /*!
7877 Sets the parent of this QCPItemPosition to \a parentAnchor. This means the
7878 position will now follow any position changes of the anchor. The local
7879 coordinate system of positions with a parent anchor always is absolute pixels,
7880 with (0, 0) being exactly on top of the parent anchor. (Hence the type
7881 shouldn't be set to \ref ptPlotCoords for positions with parent anchors.)
7882
7883 if \a keepPixelPosition is true, the current pixel position of the
7884 QCPItemPosition is preserved during reparenting. If it's set to false, the
7885 coordinates are set to (0, 0), i.e. the position will be exactly on top of the
7886 parent anchor.
7887
7888 To remove this QCPItemPosition from any parent anchor, set \a parentAnchor to
7889 0.
7890
7891 If the QCPItemPosition previously had no parent and the type is \ref
7892 ptPlotCoords, the type is set to \ref ptAbsolute, to keep the position in a
7893 valid state.
7894
7895 This method sets the parent anchor for both X and Y directions. It is also
7896 possible to set different parents for X and Y, see \ref setParentAnchorX, \ref
7897 setParentAnchorY.
7898 */
7899 bool QCPItemPosition::setParentAnchor(QCPItemAnchor *parentAnchor,
7900 bool keepPixelPosition) {
7901 bool successX = setParentAnchorX(parentAnchor, keepPixelPosition);
7902 bool successY = setParentAnchorY(parentAnchor, keepPixelPosition);
7903 return successX && successY;
7904 }
7905
7906 /*!
7907 This method sets the parent anchor of the X coordinate to \a parentAnchor.
7908
7909 For a detailed description of what a parent anchor is, see the documentation
7910 of \ref setParentAnchor.
7911
7912 \see setParentAnchor, setParentAnchorY
7913 */
7914 bool QCPItemPosition::setParentAnchorX(QCPItemAnchor *parentAnchor,
7915 bool keepPixelPosition) {
7916 // make sure self is not assigned as parent:
7917 if (parentAnchor == this) {
7918 qDebug() << Q_FUNC_INFO << "can't set self as parent anchor"
7919 << reinterpret_cast<quintptr>(parentAnchor);
7920 return false;
7921 }
7922 // make sure no recursive parent-child-relationships are created:
7923 QCPItemAnchor *currentParent = parentAnchor;
7924 while (currentParent) {
7925 if (QCPItemPosition *currentParentPos =
7926 currentParent->toQCPItemPosition()) {
7927 // is a QCPItemPosition, might have further parent, so keep iterating
7928 if (currentParentPos == this) {
7929 qDebug() << Q_FUNC_INFO
7930 << "can't create recursive parent-child-relationship"
7931 << reinterpret_cast<quintptr>(parentAnchor);
7932 return false;
7933 }
7934 currentParent = currentParentPos->parentAnchorX();
7935 } else {
7936 // is a QCPItemAnchor, can't have further parent. Now make sure the parent
7937 // items aren't the same, to prevent a position being child of an anchor
7938 // which itself depends on the position, because they're both on the same
7939 // item:
7940 if (currentParent->mParentItem == mParentItem) {
7941 qDebug() << Q_FUNC_INFO
7942 << "can't set parent to be an anchor which itself depends on "
7943 "this position"
7944 << reinterpret_cast<quintptr>(parentAnchor);
7945 return false;
7946 }
7947 break;
7948 }
7949 }
7950
7951 // if previously no parent set and PosType is still ptPlotCoords, set to
7952 // ptAbsolute:
7953 if (!mParentAnchorX && mPositionTypeX == ptPlotCoords) setTypeX(ptAbsolute);
7954
7955 // save pixel position:
7956 QPointF pixelP;
7957 if (keepPixelPosition) pixelP = pixelPoint();
7958 // unregister at current parent anchor:
7959 if (mParentAnchorX) mParentAnchorX->removeChildX(this);
7960 // register at new parent anchor:
7961 if (parentAnchor) parentAnchor->addChildX(this);
7962 mParentAnchorX = parentAnchor;
7963 // restore pixel position under new parent:
7964 if (keepPixelPosition)
7965 setPixelPoint(pixelP);
7966 else
7967 setCoords(0, coords().y());
7968 return true;
7969 }
7970
7971 /*!
7972 This method sets the parent anchor of the Y coordinate to \a parentAnchor.
7973
7974 For a detailed description of what a parent anchor is, see the documentation
7975 of \ref setParentAnchor.
7976
7977 \see setParentAnchor, setParentAnchorX
7978 */
7979 bool QCPItemPosition::setParentAnchorY(QCPItemAnchor *parentAnchor,
7980 bool keepPixelPosition) {
7981 // make sure self is not assigned as parent:
7982 if (parentAnchor == this) {
7983 qDebug() << Q_FUNC_INFO << "can't set self as parent anchor"
7984 << reinterpret_cast<quintptr>(parentAnchor);
7985 return false;
7986 }
7987 // make sure no recursive parent-child-relationships are created:
7988 QCPItemAnchor *currentParent = parentAnchor;
7989 while (currentParent) {
7990 if (QCPItemPosition *currentParentPos =
7991 currentParent->toQCPItemPosition()) {
7992 // is a QCPItemPosition, might have further parent, so keep iterating
7993 if (currentParentPos == this) {
7994 qDebug() << Q_FUNC_INFO
7995 << "can't create recursive parent-child-relationship"
7996 << reinterpret_cast<quintptr>(parentAnchor);
7997 return false;
7998 }
7999 currentParent = currentParentPos->parentAnchorY();
8000 } else {
8001 // is a QCPItemAnchor, can't have further parent. Now make sure the parent
8002 // items aren't the same, to prevent a position being child of an anchor
8003 // which itself depends on the position, because they're both on the same
8004 // item:
8005 if (currentParent->mParentItem == mParentItem) {
8006 qDebug() << Q_FUNC_INFO
8007 << "can't set parent to be an anchor which itself depends on "
8008 "this position"
8009 << reinterpret_cast<quintptr>(parentAnchor);
8010 return false;
8011 }
8012 break;
8013 }
8014 }
8015
8016 // if previously no parent set and PosType is still ptPlotCoords, set to
8017 // ptAbsolute:
8018 if (!mParentAnchorY && mPositionTypeY == ptPlotCoords) setTypeY(ptAbsolute);
8019
8020 // save pixel position:
8021 QPointF pixelP;
8022 if (keepPixelPosition) pixelP = pixelPoint();
8023 // unregister at current parent anchor:
8024 if (mParentAnchorY) mParentAnchorY->removeChildY(this);
8025 // register at new parent anchor:
8026 if (parentAnchor) parentAnchor->addChildY(this);
8027 mParentAnchorY = parentAnchor;
8028 // restore pixel position under new parent:
8029 if (keepPixelPosition)
8030 setPixelPoint(pixelP);
8031 else
8032 setCoords(coords().x(), 0);
8033 return true;
8034 }
8035
8036 /*!
8037 Sets the coordinates of this QCPItemPosition. What the coordinates mean, is
8038 defined by the type
8039 (\ref setType, \ref setTypeX, \ref setTypeY).
8040
8041 For example, if the type is \ref ptAbsolute, \a key and \a value mean the x
8042 and y pixel position on the QCustomPlot surface. In that case the origin (0,
8043 0) is in the top left corner of the QCustomPlot viewport. If the type is \ref
8044 ptPlotCoords, \a key and \a value mean a point in the plot coordinate system
8045 defined by the axes set by \ref setAxes. By default those are the
8046 QCustomPlot's xAxis and yAxis. See the documentation of \ref setType for other
8047 available coordinate types and their meaning.
8048
8049 If different types were configured for X and Y (\ref setTypeX, \ref setTypeY),
8050 \a key and \a value must also be provided in the different coordinate systems.
8051 Here, the X type refers to \a key, and the Y type refers to \a value.
8052
8053 \see setPixelPoint
8054 */
8055 void QCPItemPosition::setCoords(double key, double value) {
8056 mKey = key;
8057 mValue = value;
8058 }
8059
8060 /*! \overload
8061
8062 Sets the coordinates as a QPointF \a pos where pos.x has the meaning of \a key
8063 and pos.y the meaning of \a value of the \ref setCoords(double key, double
8064 value) method.
8065 */
8066 void QCPItemPosition::setCoords(const QPointF &pos) {
8067 setCoords(pos.x(), pos.y());
8068 }
8069
8070 /*!
8071 Returns the final absolute pixel position of the QCPItemPosition on the
8072 QCustomPlot surface. It includes all effects of type (\ref setType) and
8073 possible parent anchors (\ref setParentAnchor).
8074
8075 \see setPixelPoint
8076 */
8077 QPointF QCPItemPosition::pixelPoint() const {
8078 QPointF result;
8079
8080 // determine X:
8081 switch (mPositionTypeX) {
8082 case ptAbsolute: {
8083 result.rx() = mKey;
8084 if (mParentAnchorX) result.rx() += mParentAnchorX->pixelPoint().x();
8085 break;
8086 }
8087 case ptViewportRatio: {
8088 result.rx() = mKey * mParentPlot->viewport().width();
8089 if (mParentAnchorX)
8090 result.rx() += mParentAnchorX->pixelPoint().x();
8091 else
8092 result.rx() += mParentPlot->viewport().left();
8093 break;
8094 }
8095 case ptAxisRectRatio: {
8096 if (mAxisRect) {
8097 result.rx() = mKey * mAxisRect.data()->width();
8098 if (mParentAnchorX)
8099 result.rx() += mParentAnchorX->pixelPoint().x();
8100 else
8101 result.rx() += mAxisRect.data()->left();
8102 } else
8103 qDebug() << Q_FUNC_INFO
8104 << "Item position type x is ptAxisRectRatio, but no axis rect "
8105 "was defined";
8106 break;
8107 }
8108 case ptPlotCoords: {
8109 if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Horizontal)
8110 result.rx() = mKeyAxis.data()->coordToPixel(mKey);
8111 else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Horizontal)
8112 result.rx() = mValueAxis.data()->coordToPixel(mValue);
8113 else
8114 qDebug()
8115 << Q_FUNC_INFO
8116 << "Item position type x is ptPlotCoords, but no axes were defined";
8117 break;
8118 }
8119 }
8120
8121 // determine Y:
8122 switch (mPositionTypeY) {
8123 case ptAbsolute: {
8124 result.ry() = mValue;
8125 if (mParentAnchorY) result.ry() += mParentAnchorY->pixelPoint().y();
8126 break;
8127 }
8128 case ptViewportRatio: {
8129 result.ry() = mValue * mParentPlot->viewport().height();
8130 if (mParentAnchorY)
8131 result.ry() += mParentAnchorY->pixelPoint().y();
8132 else
8133 result.ry() += mParentPlot->viewport().top();
8134 break;
8135 }
8136 case ptAxisRectRatio: {
8137 if (mAxisRect) {
8138 result.ry() = mValue * mAxisRect.data()->height();
8139 if (mParentAnchorY)
8140 result.ry() += mParentAnchorY->pixelPoint().y();
8141 else
8142 result.ry() += mAxisRect.data()->top();
8143 } else
8144 qDebug() << Q_FUNC_INFO
8145 << "Item position type y is ptAxisRectRatio, but no axis rect "
8146 "was defined";
8147 break;
8148 }
8149 case ptPlotCoords: {
8150 if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Vertical)
8151 result.ry() = mKeyAxis.data()->coordToPixel(mKey);
8152 else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Vertical)
8153 result.ry() = mValueAxis.data()->coordToPixel(mValue);
8154 else
8155 qDebug()
8156 << Q_FUNC_INFO
8157 << "Item position type y is ptPlotCoords, but no axes were defined";
8158 break;
8159 }
8160 }
8161
8162 return result;
8163 }
8164
8165 /*!
8166 When \ref setType is \ref ptPlotCoords, this function may be used to specify
8167 the axes the coordinates set with \ref setCoords relate to. By default they
8168 are set to the initial xAxis and yAxis of the QCustomPlot.
8169 */
8170 void QCPItemPosition::setAxes(QCPAxis *keyAxis, QCPAxis *valueAxis) {
8171 mKeyAxis = keyAxis;
8172 mValueAxis = valueAxis;
8173 }
8174
8175 /*!
8176 When \ref setType is \ref ptAxisRectRatio, this function may be used to
8177 specify the axis rect the coordinates set with \ref setCoords relate to. By
8178 default this is set to the main axis rect of the QCustomPlot.
8179 */
8180 void QCPItemPosition::setAxisRect(QCPAxisRect *axisRect) {
8181 mAxisRect = axisRect;
8182 }
8183
8184 /*!
8185 Sets the apparent pixel position. This works no matter what type (\ref
8186 setType) this QCPItemPosition is or what parent-child situation it is in, as
8187 coordinates are transformed appropriately, to make the position finally appear
8188 at the specified pixel values.
8189
8190 Only if the type is \ref ptAbsolute and no parent anchor is set, this
8191 function's effect is identical to that of \ref setCoords.
8192
8193 \see pixelPoint, setCoords
8194 */
8195 void QCPItemPosition::setPixelPoint(const QPointF &pixelPoint) {
8196 double x = pixelPoint.x();
8197 double y = pixelPoint.y();
8198
8199 switch (mPositionTypeX) {
8200 case ptAbsolute: {
8201 if (mParentAnchorX) x -= mParentAnchorX->pixelPoint().x();
8202 break;
8203 }
8204 case ptViewportRatio: {
8205 if (mParentAnchorX)
8206 x -= mParentAnchorX->pixelPoint().x();
8207 else
8208 x -= mParentPlot->viewport().left();
8209 x /= (double)mParentPlot->viewport().width();
8210 break;
8211 }
8212 case ptAxisRectRatio: {
8213 if (mAxisRect) {
8214 if (mParentAnchorX)
8215 x -= mParentAnchorX->pixelPoint().x();
8216 else
8217 x -= mAxisRect.data()->left();
8218 x /= (double)mAxisRect.data()->width();
8219 } else
8220 qDebug() << Q_FUNC_INFO
8221 << "Item position type x is ptAxisRectRatio, but no axis rect "
8222 "was defined";
8223 break;
8224 }
8225 case ptPlotCoords: {
8226 if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Horizontal)
8227 x = mKeyAxis.data()->pixelToCoord(x);
8228 else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Horizontal)
8229 y = mValueAxis.data()->pixelToCoord(x);
8230 else
8231 qDebug()
8232 << Q_FUNC_INFO
8233 << "Item position type x is ptPlotCoords, but no axes were defined";
8234 break;
8235 }
8236 }
8237
8238 switch (mPositionTypeY) {
8239 case ptAbsolute: {
8240 if (mParentAnchorY) y -= mParentAnchorY->pixelPoint().y();
8241 break;
8242 }
8243 case ptViewportRatio: {
8244 if (mParentAnchorY)
8245 y -= mParentAnchorY->pixelPoint().y();
8246 else
8247 y -= mParentPlot->viewport().top();
8248 y /= (double)mParentPlot->viewport().height();
8249 break;
8250 }
8251 case ptAxisRectRatio: {
8252 if (mAxisRect) {
8253 if (mParentAnchorY)
8254 y -= mParentAnchorY->pixelPoint().y();
8255 else
8256 y -= mAxisRect.data()->top();
8257 y /= (double)mAxisRect.data()->height();
8258 } else
8259 qDebug() << Q_FUNC_INFO
8260 << "Item position type y is ptAxisRectRatio, but no axis rect "
8261 "was defined";
8262 break;
8263 }
8264 case ptPlotCoords: {
8265 if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Vertical)
8266 x = mKeyAxis.data()->pixelToCoord(y);
8267 else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Vertical)
8268 y = mValueAxis.data()->pixelToCoord(y);
8269 else
8270 qDebug()
8271 << Q_FUNC_INFO
8272 << "Item position type y is ptPlotCoords, but no axes were defined";
8273 break;
8274 }
8275 }
8276
8277 setCoords(x, y);
8278 }
8279
8280 ////////////////////////////////////////////////////////////////////////////////////////////////////
8281 //////////////////// QCPAbstractItem
8282 ////////////////////////////////////////////////////////////////////////////////////////////////////
8283
8284 /*! \class QCPAbstractItem
8285 \brief The abstract base class for all items in a plot.
8286
8287 In QCustomPlot, items are supplemental graphical elements that are neither
8288 plottables (QCPAbstractPlottable) nor axes (QCPAxis). While plottables are
8289 always tied to two axes and thus plot coordinates, items can also be placed in
8290 absolute coordinates independent of any axes. Each specific item has at least
8291 one QCPItemPosition member which controls the positioning. Some items are
8292 defined by more than one coordinate and thus have two or more QCPItemPosition
8293 members (For example, QCPItemRect has \a topLeft and \a bottomRight).
8294
8295 This abstract base class defines a very basic interface like visibility and
8296 clipping. Since this class is abstract, it can't be instantiated. Use one of
8297 the subclasses or create a subclass yourself to create new items.
8298
8299 The built-in items are:
8300 <table>
8301 <tr><td>QCPItemLine</td><td>A line defined by a start and an end point. May
8302 have different ending styles on each side (e.g. arrows).</td></tr>
8303 <tr><td>QCPItemStraightLine</td><td>A straight line defined by a start and a
8304 direction point. Unlike QCPItemLine, the straight line is infinitely long and
8305 has no endings.</td></tr> <tr><td>QCPItemCurve</td><td>A curve defined by
8306 start, end and two intermediate control points. May have different ending
8307 styles on each side (e.g. arrows).</td></tr> <tr><td>QCPItemRect</td><td>A
8308 rectangle</td></tr> <tr><td>QCPItemEllipse</td><td>An ellipse</td></tr>
8309 <tr><td>QCPItemPixmap</td><td>An arbitrary pixmap</td></tr>
8310 <tr><td>QCPItemText</td><td>A text label</td></tr>
8311 <tr><td>QCPItemBracket</td><td>A bracket which may be used to
8312 reference/highlight certain parts in the plot.</td></tr>
8313 <tr><td>QCPItemTracer</td><td>An item that can be attached to a QCPGraph and
8314 sticks to its data points, given a key coordinate.</td></tr>
8315 </table>
8316
8317 \section items-clipping Clipping
8318
8319 Items are by default clipped to the main axis rect (they are only visible
8320 inside the axis rect). To make an item visible outside that axis rect, disable
8321 clipping via \ref setClipToAxisRect "setClipToAxisRect(false)".
8322
8323 On the other hand if you want the item to be clipped to a different axis rect,
8324 specify it via \ref setClipAxisRect. This clipAxisRect property of an item is
8325 only used for clipping behaviour, and in principle is independent of the
8326 coordinate axes the item might be tied to via its position members (\ref
8327 QCPItemPosition::setAxes). However, it is common that the axis rect for
8328 clipping also contains the axes used for the item positions.
8329
8330 \section items-using Using items
8331
8332 First you instantiate the item you want to use and add it to the plot:
8333 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-1
8334 by default, the positions of the item are bound to the x- and y-Axis of the
8335 plot. So we can just set the plot coordinates where the line should start/end:
8336 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-2
8337 If we don't want the line to be positioned in plot coordinates but a different
8338 coordinate system, e.g. absolute pixel positions on the QCustomPlot surface,
8339 we need to change the position type like this: \snippet
8340 documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-3 Then we
8341 can set the coordinates, this time in pixels: \snippet
8342 documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-4 and make
8343 the line visible on the entire QCustomPlot, by disabling clipping to the axis
8344 rect: \snippet documentation/doc-code-snippets/mainwindow.cpp
8345 qcpitemline-creation-5
8346
8347 For more advanced plots, it is even possible to set different types and parent
8348 anchors per X/Y coordinate of an item position, using for example \ref
8349 QCPItemPosition::setTypeX or \ref QCPItemPosition::setParentAnchorX. For
8350 details, see the documentation of \ref QCPItemPosition.
8351
8352 \section items-subclassing Creating own items
8353
8354 To create an own item, you implement a subclass of QCPAbstractItem. These are
8355 the pure virtual functions, you must implement: \li \ref selectTest \li \ref
8356 draw
8357
8358 See the documentation of those functions for what they need to do.
8359
8360 \subsection items-positioning Allowing the item to be positioned
8361
8362 As mentioned, item positions are represented by QCPItemPosition members. Let's
8363 assume the new item shall have only one point as its position (as opposed to
8364 two like a rect or multiple like a polygon). You then add a public member of
8365 type QCPItemPosition like so:
8366
8367 \code QCPItemPosition * const myPosition;\endcode
8368
8369 the const makes sure the pointer itself can't be modified from the user of
8370 your new item (the QCPItemPosition instance it points to, can be modified, of
8371 course). The initialization of this pointer is made easy with the \ref
8372 createPosition function. Just assign the return value of this function to each
8373 QCPItemPosition in the constructor of your item. \ref createPosition takes a
8374 string which is the name of the position, typically this is identical to the
8375 variable name. For example, the constructor of QCPItemExample could look like
8376 this:
8377
8378 \code
8379 QCPItemExample::QCPItemExample(QCustomPlot *parentPlot) :
8380 QCPAbstractItem(parentPlot),
8381 myPosition(createPosition("myPosition"))
8382 {
8383 // other constructor code
8384 }
8385 \endcode
8386
8387 \subsection items-drawing The draw function
8388
8389 To give your item a visual representation, reimplement the \ref draw function
8390 and use the passed QCPPainter to draw the item. You can retrieve the item
8391 position in pixel coordinates from the position member(s) via \ref
8392 QCPItemPosition::pixelPoint.
8393
8394 To optimize performance you should calculate a bounding rect first (don't
8395 forget to take the pen width into account), check whether it intersects the
8396 \ref clipRect, and only draw the item at all if this is the case.
8397
8398 \subsection items-selection The selectTest function
8399
8400 Your implementation of the \ref selectTest function may use the helpers \ref
8401 distSqrToLine and \ref rectSelectTest. With these, the implementation of the
8402 selection test becomes significantly simpler for most items. See the
8403 documentation of \ref selectTest for what the function parameters mean and
8404 what the function should return.
8405
8406 \subsection anchors Providing anchors
8407
8408 Providing anchors (QCPItemAnchor) starts off like adding a position. First you
8409 create a public member, e.g.
8410
8411 \code QCPItemAnchor * const bottom;\endcode
8412
8413 and create it in the constructor with the \ref createAnchor function,
8414 assigning it a name and an anchor id (an integer enumerating all anchors on
8415 the item, you may create an own enum for this). Since anchors can be placed
8416 anywhere, relative to the item's position(s), your item needs to provide the
8417 position of every anchor with the reimplementation of the \ref
8418 anchorPixelPoint(int anchorId) function.
8419
8420 In essence the QCPItemAnchor is merely an intermediary that itself asks your
8421 item for the pixel position when anything attached to the anchor needs to know
8422 the coordinates.
8423 */
8424
8425 /* start of documentation of inline functions */
8426
8427 /*! \fn QList<QCPItemPosition*> QCPAbstractItem::positions() const
8428
8429 Returns all positions of the item in a list.
8430
8431 \see anchors, position
8432 */
8433
8434 /*! \fn QList<QCPItemAnchor*> QCPAbstractItem::anchors() const
8435
8436 Returns all anchors of the item in a list. Note that since a position
8437 (QCPItemPosition) is always also an anchor, the list will also contain the
8438 positions of this item.
8439
8440 \see positions, anchor
8441 */
8442
8443 /* end of documentation of inline functions */
8444 /* start documentation of pure virtual functions */
8445
8446 /*! \fn void QCPAbstractItem::draw(QCPPainter *painter) = 0
8447 \internal
8448
8449 Draws this item with the provided \a painter.
8450
8451 The cliprect of the provided painter is set to the rect returned by \ref
8452 clipRect before this function is called. The clipRect depends on the clipping
8453 settings defined by \ref setClipToAxisRect and \ref setClipAxisRect.
8454 */
8455
8456 /* end documentation of pure virtual functions */
8457 /* start documentation of signals */
8458
8459 /*! \fn void QCPAbstractItem::selectionChanged(bool selected)
8460 This signal is emitted when the selection state of this item has changed,
8461 either by user interaction or by a direct call to \ref setSelected.
8462 */
8463
8464 /* end documentation of signals */
8465
8466 /*!
8467 Base class constructor which initializes base class members.
8468 */
8469 QCPAbstractItem::QCPAbstractItem(QCustomPlot *parentPlot)
8470 : QCPLayerable(parentPlot),
8471 mClipToAxisRect(false),
8472 mSelectable(true),
8473 mSelected(false) {
8474 QList<QCPAxisRect *> rects = parentPlot->axisRects();
8475 if (rects.size() > 0) {
8476 setClipToAxisRect(true);
8477 setClipAxisRect(rects.first());
8478 }
8479 }
8480
8481 QCPAbstractItem::~QCPAbstractItem() {
8482 // don't delete mPositions because every position is also an anchor and thus
8483 // in mAnchors
8484 qDeleteAll(mAnchors);
8485 }
8486
8487 /* can't make this a header inline function, because QPointer breaks with
8488 * forward declared types, see QTBUG-29588 */
8489 QCPAxisRect *QCPAbstractItem::clipAxisRect() const {
8490 return mClipAxisRect.data();
8491 }
8492
8493 /*!
8494 Sets whether the item shall be clipped to an axis rect or whether it shall be
8495 visible on the entire QCustomPlot. The axis rect can be set with \ref
8496 setClipAxisRect.
8497
8498 \see setClipAxisRect
8499 */
8500 void QCPAbstractItem::setClipToAxisRect(bool clip) {
8501 mClipToAxisRect = clip;
8502 if (mClipToAxisRect) setParentLayerable(mClipAxisRect.data());
8503 }
8504
8505 /*!
8506 Sets the clip axis rect. It defines the rect that will be used to clip the
8507 item when \ref setClipToAxisRect is set to true.
8508
8509 \see setClipToAxisRect
8510 */
8511 void QCPAbstractItem::setClipAxisRect(QCPAxisRect *rect) {
8512 mClipAxisRect = rect;
8513 if (mClipToAxisRect) setParentLayerable(mClipAxisRect.data());
8514 }
8515
8516 /*!
8517 Sets whether the user can (de-)select this item by clicking on the QCustomPlot
8518 surface. (When \ref QCustomPlot::setInteractions contains
8519 QCustomPlot::iSelectItems.)
8520
8521 However, even when \a selectable was set to false, it is possible to set the
8522 selection manually, by calling \ref setSelected.
8523
8524 \see QCustomPlot::setInteractions, setSelected
8525 */
8526 void QCPAbstractItem::setSelectable(bool selectable) {
8527 if (mSelectable != selectable) {
8528 mSelectable = selectable;
8529 emit selectableChanged(mSelectable);
8530 }
8531 }
8532
8533 /*!
8534 Sets whether this item is selected or not. When selected, it might use a
8535 different visual appearance (e.g. pen and brush), this depends on the specific
8536 item though.
8537
8538 The entire selection mechanism for items is handled automatically when \ref
8539 QCustomPlot::setInteractions contains QCustomPlot::iSelectItems. You only need
8540 to call this function when you wish to change the selection state manually.
8541
8542 This function can change the selection state even when \ref setSelectable was
8543 set to false.
8544
8545 emits the \ref selectionChanged signal when \a selected is different from the
8546 previous selection state.
8547
8548 \see setSelectable, selectTest
8549 */
8550 void QCPAbstractItem::setSelected(bool selected) {
8551 if (mSelected != selected) {
8552 mSelected = selected;
8553 emit selectionChanged(mSelected);
8554 }
8555 }
8556
8557 /*!
8558 Returns the QCPItemPosition with the specified \a name. If this item doesn't
8559 have a position by that name, returns 0.
8560
8561 This function provides an alternative way to access item positions. Normally,
8562 you access positions direcly by their member pointers (which typically have
8563 the same variable name as \a name).
8564
8565 \see positions, anchor
8566 */
8567 QCPItemPosition *QCPAbstractItem::position(const QString &name) const {
8568 for (int i = 0; i < mPositions.size(); ++i) {
8569 if (mPositions.at(i)->name() == name) return mPositions.at(i);
8570 }
8571 qDebug() << Q_FUNC_INFO << "position with name not found:" << name;
8572 return 0;
8573 }
8574
8575 /*!
8576 Returns the QCPItemAnchor with the specified \a name. If this item doesn't
8577 have an anchor by that name, returns 0.
8578
8579 This function provides an alternative way to access item anchors. Normally,
8580 you access anchors direcly by their member pointers (which typically have the
8581 same variable name as \a name).
8582
8583 \see anchors, position
8584 */
8585 QCPItemAnchor *QCPAbstractItem::anchor(const QString &name) const {
8586 for (int i = 0; i < mAnchors.size(); ++i) {
8587 if (mAnchors.at(i)->name() == name) return mAnchors.at(i);
8588 }
8589 qDebug() << Q_FUNC_INFO << "anchor with name not found:" << name;
8590 return 0;
8591 }
8592
8593 /*!
8594 Returns whether this item has an anchor with the specified \a name.
8595
8596 Note that you can check for positions with this function, too. This is because
8597 every position is also an anchor (QCPItemPosition inherits from
8598 QCPItemAnchor).
8599
8600 \see anchor, position
8601 */
8602 bool QCPAbstractItem::hasAnchor(const QString &name) const {
8603 for (int i = 0; i < mAnchors.size(); ++i) {
8604 if (mAnchors.at(i)->name() == name) return true;
8605 }
8606 return false;
8607 }
8608
8609 /*! \internal
8610
8611 Returns the rect the visual representation of this item is clipped to. This
8612 depends on the current setting of \ref setClipToAxisRect as well as the axis
8613 rect set with \ref setClipAxisRect.
8614
8615 If the item is not clipped to an axis rect, the \ref QCustomPlot::viewport
8616 rect is returned.
8617
8618 \see draw
8619 */
8620 QRect QCPAbstractItem::clipRect() const {
8621 if (mClipToAxisRect && mClipAxisRect)
8622 return mClipAxisRect.data()->rect();
8623 else
8624 return mParentPlot->viewport();
8625 }
8626
8627 /*! \internal
8628
8629 A convenience function to easily set the QPainter::Antialiased hint on the
8630 provided \a painter before drawing item lines.
8631
8632 This is the antialiasing state the painter passed to the \ref draw method is
8633 in by default.
8634
8635 This function takes into account the local setting of the antialiasing flag as
8636 well as the overrides set with \ref QCustomPlot::setAntialiasedElements and
8637 \ref QCustomPlot::setNotAntialiasedElements.
8638
8639 \see setAntialiased
8640 */
8641 void QCPAbstractItem::applyDefaultAntialiasingHint(QCPPainter *painter) const {
8642 applyAntialiasingHint(painter, mAntialiased, QCP::aeItems);
8643 }
8644
8645 /*! \internal
8646
8647 Finds the shortest squared distance of \a point to the line segment defined by
8648 \a start and \a end.
8649
8650 This function may be used to help with the implementation of the \ref
8651 selectTest function for specific items.
8652
8653 \note This function is identical to QCPAbstractPlottable::distSqrToLine
8654
8655 \see rectSelectTest
8656 */
8657 double QCPAbstractItem::distSqrToLine(const QPointF &start, const QPointF &end,
8658 const QPointF &point) const {
8659 QVector2D a(start);
8660 QVector2D b(end);
8661 QVector2D p(point);
8662 QVector2D v(b - a);
8663
8664 double vLengthSqr = v.lengthSquared();
8665 if (!qFuzzyIsNull(vLengthSqr)) {
8666 double mu = QVector2D::dotProduct(p - a, v) / vLengthSqr;
8667 if (mu < 0)
8668 return (a - p).lengthSquared();
8669 else if (mu > 1)
8670 return (b - p).lengthSquared();
8671 else
8672 return ((a + mu * v) - p).lengthSquared();
8673 } else
8674 return (a - p).lengthSquared();
8675 }
8676
8677 /*! \internal
8678
8679 A convenience function which returns the selectTest value for a specified \a
8680 rect and a specified click position \a pos. \a filledRect defines whether a
8681 click inside the rect should also be considered a hit or whether only the rect
8682 border is sensitive to hits.
8683
8684 This function may be used to help with the implementation of the \ref
8685 selectTest function for specific items.
8686
8687 For example, if your item consists of four rects, call this function four
8688 times, once for each rect, in your \ref selectTest reimplementation. Finally,
8689 return the minimum of all four returned values.
8690
8691 \see distSqrToLine
8692 */
8693 double QCPAbstractItem::rectSelectTest(const QRectF &rect, const QPointF &pos,
8694 bool filledRect) const {
8695 double result = -1;
8696
8697 // distance to border:
8698 QList<QLineF> lines;
8699 lines << QLineF(rect.topLeft(), rect.topRight())
8700 << QLineF(rect.bottomLeft(), rect.bottomRight())
8701 << QLineF(rect.topLeft(), rect.bottomLeft())
8702 << QLineF(rect.topRight(), rect.bottomRight());
8703 double minDistSqr = std::numeric_limits<double>::max();
8704 for (int i = 0; i < lines.size(); ++i) {
8705 double distSqr = distSqrToLine(lines.at(i).p1(), lines.at(i).p2(), pos);
8706 if (distSqr < minDistSqr) minDistSqr = distSqr;
8707 }
8708 result = qSqrt(minDistSqr);
8709
8710 // filled rect, allow click inside to count as hit:
8711 if (filledRect && result > mParentPlot->selectionTolerance() * 0.99) {
8712 if (rect.contains(pos)) result = mParentPlot->selectionTolerance() * 0.99;
8713 }
8714 return result;
8715 }
8716
8717 /*! \internal
8718
8719 Returns the pixel position of the anchor with Id \a anchorId. This function
8720 must be reimplemented in item subclasses if they want to provide anchors
8721 (QCPItemAnchor).
8722
8723 For example, if the item has two anchors with id 0 and 1, this function takes
8724 one of these anchor ids and returns the respective pixel points of the
8725 specified anchor.
8726
8727 \see createAnchor
8728 */
8729 QPointF QCPAbstractItem::anchorPixelPoint(int anchorId) const {
8730 qDebug() << Q_FUNC_INFO
8731 << "called on item which shouldn't have any anchors (this method "
8732 "not reimplemented). anchorId"
8733 << anchorId;
8734 return QPointF();
8735 }
8736
8737 /*! \internal
8738
8739 Creates a QCPItemPosition, registers it with this item and returns a pointer
8740 to it. The specified \a name must be a unique string that is usually identical
8741 to the variable name of the position member (This is needed to provide the
8742 name-based \ref position access to positions).
8743
8744 Don't delete positions created by this function manually, as the item will
8745 take care of it.
8746
8747 Use this function in the constructor (initialization list) of the specific
8748 item subclass to create each position member. Don't create QCPItemPositions
8749 with \b new yourself, because they won't be registered with the item properly.
8750
8751 \see createAnchor
8752 */
8753 QCPItemPosition *QCPAbstractItem::createPosition(const QString &name) {
8754 if (hasAnchor(name))
8755 qDebug() << Q_FUNC_INFO
8756 << "anchor/position with name exists already:" << name;
8757 QCPItemPosition *newPosition = new QCPItemPosition(mParentPlot, this, name);
8758 mPositions.append(newPosition);
8759 mAnchors.append(newPosition); // every position is also an anchor
8760 newPosition->setAxes(mParentPlot->xAxis, mParentPlot->yAxis);
8761 newPosition->setType(QCPItemPosition::ptPlotCoords);
8762 if (mParentPlot->axisRect())
8763 newPosition->setAxisRect(mParentPlot->axisRect());
8764 newPosition->setCoords(0, 0);
8765 return newPosition;
8766 }
8767
8768 /*! \internal
8769
8770 Creates a QCPItemAnchor, registers it with this item and returns a pointer to
8771 it. The specified \a name must be a unique string that is usually identical to
8772 the variable name of the anchor member (This is needed to provide the name
8773 based \ref anchor access to anchors).
8774
8775 The \a anchorId must be a number identifying the created anchor. It is
8776 recommended to create an enum (e.g. "AnchorIndex") for this on each item that
8777 uses anchors. This id is used by the anchor to identify itself when it calls
8778 QCPAbstractItem::anchorPixelPoint. That function then returns the correct
8779 pixel coordinates for the passed anchor id.
8780
8781 Don't delete anchors created by this function manually, as the item will take
8782 care of it.
8783
8784 Use this function in the constructor (initialization list) of the specific
8785 item subclass to create each anchor member. Don't create QCPItemAnchors with
8786 \b new yourself, because then they won't be registered with the item properly.
8787
8788 \see createPosition
8789 */
8790 QCPItemAnchor *QCPAbstractItem::createAnchor(const QString &name,
8791 int anchorId) {
8792 if (hasAnchor(name))
8793 qDebug() << Q_FUNC_INFO
8794 << "anchor/position with name exists already:" << name;
8795 QCPItemAnchor *newAnchor =
8796 new QCPItemAnchor(mParentPlot, this, name, anchorId);
8797 mAnchors.append(newAnchor);
8798 return newAnchor;
8799 }
8800
8801 /* inherits documentation from base class */
8802 void QCPAbstractItem::selectEvent(QMouseEvent *event, bool additive,
8803 const QVariant &details,
8804 bool *selectionStateChanged) {
8805 Q_UNUSED(event)
8806 Q_UNUSED(details)
8807 if (mSelectable) {
8808 bool selBefore = mSelected;
8809 setSelected(additive ? !mSelected : true);
8810 if (selectionStateChanged) *selectionStateChanged = mSelected != selBefore;
8811 }
8812 }
8813
8814 /* inherits documentation from base class */
8815 void QCPAbstractItem::deselectEvent(bool *selectionStateChanged) {
8816 if (mSelectable) {
8817 bool selBefore = mSelected;
8818 setSelected(false);
8819 if (selectionStateChanged) *selectionStateChanged = mSelected != selBefore;
8820 }
8821 }
8822
8823 /* inherits documentation from base class */
8824 QCP::Interaction QCPAbstractItem::selectionCategory() const {
8825 return QCP::iSelectItems;
8826 }
8827
8828 /*! \file */
8829
8830 ////////////////////////////////////////////////////////////////////////////////////////////////////
8831 //////////////////// QCustomPlot
8832 ////////////////////////////////////////////////////////////////////////////////////////////////////
8833
8834 /*! \class QCustomPlot
8835
8836 \brief The central class of the library. This is the QWidget which displays
8837 the plot and interacts with the user.
8838
8839 For tutorials on how to use QCustomPlot, see the website\n
8840 http://www.qcustomplot.com/
8841 */
8842
8843 /* start of documentation of inline functions */
8844
8845 /*! \fn QRect QCustomPlot::viewport() const
8846
8847 Returns the viewport rect of this QCustomPlot instance. The viewport is the
8848 area the plot is drawn in, all mechanisms, e.g. margin caluclation take the
8849 viewport to be the outer border of the plot. The viewport normally is the
8850 rect() of the QCustomPlot widget, i.e. a rect with top left (0, 0) and size of
8851 the QCustomPlot widget.
8852
8853 Don't confuse the viewport with the axis rect (QCustomPlot::axisRect). An axis
8854 rect is typically an area enclosed by four axes, where the graphs/plottables
8855 are drawn in. The viewport is larger and contains also the axes themselves,
8856 their tick numbers, their labels, the plot title etc.
8857
8858 Only when saving to a file (see \ref savePng, \ref savePdf etc.) the viewport
8859 is temporarily modified to allow saving plots with sizes independent of the
8860 current widget size.
8861 */
8862
8863 /*! \fn QCPLayoutGrid *QCustomPlot::plotLayout() const
8864
8865 Returns the top level layout of this QCustomPlot instance. It is a \ref
8866 QCPLayoutGrid, initially containing just one cell with the main QCPAxisRect
8867 inside.
8868 */
8869
8870 /* end of documentation of inline functions */
8871 /* start of documentation of signals */
8872
8873 /*! \fn void QCustomPlot::mouseDoubleClick(QMouseEvent *event)
8874
8875 This signal is emitted when the QCustomPlot receives a mouse double click
8876 event.
8877 */
8878
8879 /*! \fn void QCustomPlot::mousePress(QMouseEvent *event)
8880
8881 This signal is emitted when the QCustomPlot receives a mouse press event.
8882
8883 It is emitted before QCustomPlot handles any other mechanism like range
8884 dragging. So a slot connected to this signal can still influence the behaviour
8885 e.g. with \ref QCPAxisRect::setRangeDrag or \ref
8886 QCPAxisRect::setRangeDragAxes.
8887 */
8888
8889 /*! \fn void QCustomPlot::mouseMove(QMouseEvent *event)
8890
8891 This signal is emitted when the QCustomPlot receives a mouse move event.
8892
8893 It is emitted before QCustomPlot handles any other mechanism like range
8894 dragging. So a slot connected to this signal can still influence the behaviour
8895 e.g. with \ref QCPAxisRect::setRangeDrag or \ref
8896 QCPAxisRect::setRangeDragAxes.
8897
8898 \warning It is discouraged to change the drag-axes with \ref
8899 QCPAxisRect::setRangeDragAxes here, because the dragging starting point was
8900 saved the moment the mouse was pressed. Thus it only has a meaning for the
8901 range drag axes that were set at that moment. If you want to change the drag
8902 axes, consider doing this in the \ref mousePress signal instead.
8903 */
8904
8905 /*! \fn void QCustomPlot::mouseRelease(QMouseEvent *event)
8906
8907 This signal is emitted when the QCustomPlot receives a mouse release event.
8908
8909 It is emitted before QCustomPlot handles any other mechanisms like object
8910 selection. So a slot connected to this signal can still influence the
8911 behaviour e.g. with \ref setInteractions or \ref
8912 QCPAbstractPlottable::setSelectable.
8913 */
8914
8915 /*! \fn void QCustomPlot::mouseWheel(QMouseEvent *event)
8916
8917 This signal is emitted when the QCustomPlot receives a mouse wheel event.
8918
8919 It is emitted before QCustomPlot handles any other mechanisms like range
8920 zooming. So a slot connected to this signal can still influence the behaviour
8921 e.g. with \ref QCPAxisRect::setRangeZoom, \ref QCPAxisRect::setRangeZoomAxes
8922 or \ref QCPAxisRect::setRangeZoomFactor.
8923 */
8924
8925 /*! \fn void QCustomPlot::plottableClick(QCPAbstractPlottable *plottable,
8926 QMouseEvent *event)
8927
8928 This signal is emitted when a plottable is clicked.
8929
8930 \a event is the mouse event that caused the click and \a plottable is the
8931 plottable that received the click.
8932
8933 \see plottableDoubleClick
8934 */
8935
8936 /*! \fn void QCustomPlot::plottableDoubleClick(QCPAbstractPlottable *plottable,
8937 QMouseEvent *event)
8938
8939 This signal is emitted when a plottable is double clicked.
8940
8941 \a event is the mouse event that caused the click and \a plottable is the
8942 plottable that received the click.
8943
8944 \see plottableClick
8945 */
8946
8947 /*! \fn void QCustomPlot::itemClick(QCPAbstractItem *item, QMouseEvent *event)
8948
8949 This signal is emitted when an item is clicked.
8950
8951 \a event is the mouse event that caused the click and \a item is the item that
8952 received the click.
8953
8954 \see itemDoubleClick
8955 */
8956
8957 /*! \fn void QCustomPlot::itemDoubleClick(QCPAbstractItem *item, QMouseEvent
8958 *event)
8959
8960 This signal is emitted when an item is double clicked.
8961
8962 \a event is the mouse event that caused the click and \a item is the item that
8963 received the click.
8964
8965 \see itemClick
8966 */
8967
8968 /*! \fn void QCustomPlot::axisClick(QCPAxis *axis, QCPAxis::SelectablePart part,
8969 QMouseEvent *event)
8970
8971 This signal is emitted when an axis is clicked.
8972
8973 \a event is the mouse event that caused the click, \a axis is the axis that
8974 received the click and \a part indicates the part of the axis that was
8975 clicked.
8976
8977 \see axisDoubleClick
8978 */
8979
8980 /*! \fn void QCustomPlot::axisDoubleClick(QCPAxis *axis, QCPAxis::SelectablePart
8981 part, QMouseEvent *event)
8982
8983 This signal is emitted when an axis is double clicked.
8984
8985 \a event is the mouse event that caused the click, \a axis is the axis that
8986 received the click and \a part indicates the part of the axis that was
8987 clicked.
8988
8989 \see axisClick
8990 */
8991
8992 /*! \fn void QCustomPlot::legendClick(QCPLegend *legend, QCPAbstractLegendItem
8993 *item, QMouseEvent *event)
8994
8995 This signal is emitted when a legend (item) is clicked.
8996
8997 \a event is the mouse event that caused the click, \a legend is the legend
8998 that received the click and \a item is the legend item that received the
8999 click. If only the legend and no item is clicked, \a item is 0. This happens
9000 for a click inside the legend padding or the space between two items.
9001
9002 \see legendDoubleClick
9003 */
9004
9005 /*! \fn void QCustomPlot::legendDoubleClick(QCPLegend *legend,
9006 QCPAbstractLegendItem *item, QMouseEvent *event)
9007
9008 This signal is emitted when a legend (item) is double clicked.
9009
9010 \a event is the mouse event that caused the click, \a legend is the legend
9011 that received the click and \a item is the legend item that received the
9012 click. If only the legend and no item is clicked, \a item is 0. This happens
9013 for a click inside the legend padding or the space between two items.
9014
9015 \see legendClick
9016 */
9017
9018 /*! \fn void QCustomPlot:: titleClick(QMouseEvent *event, QCPPlotTitle *title)
9019
9020 This signal is emitted when a plot title is clicked.
9021
9022 \a event is the mouse event that caused the click and \a title is the plot
9023 title that received the click.
9024
9025 \see titleDoubleClick
9026 */
9027
9028 /*! \fn void QCustomPlot::titleDoubleClick(QMouseEvent *event, QCPPlotTitle
9029 *title)
9030
9031 This signal is emitted when a plot title is double clicked.
9032
9033 \a event is the mouse event that caused the click and \a title is the plot
9034 title that received the click.
9035
9036 \see titleClick
9037 */
9038
9039 /*! \fn void QCustomPlot::selectionChangedByUser()
9040
9041 This signal is emitted after the user has changed the selection in the
9042 QCustomPlot, e.g. by clicking. It is not emitted when the selection state of
9043 an object has changed programmatically by a direct call to setSelected() on an
9044 object or by calling \ref deselectAll.
9045
9046 In addition to this signal, selectable objects also provide individual
9047 signals, for example QCPAxis::selectionChanged or
9048 QCPAbstractPlottable::selectionChanged. Note that those signals are emitted
9049 even if the selection state is changed programmatically.
9050
9051 See the documentation of \ref setInteractions for details about the selection
9052 mechanism.
9053
9054 \see selectedPlottables, selectedGraphs, selectedItems, selectedAxes,
9055 selectedLegends
9056 */
9057
9058 /*! \fn void QCustomPlot::beforeReplot()
9059
9060 This signal is emitted immediately before a replot takes place (caused by a
9061 call to the slot \ref replot).
9062
9063 It is safe to mutually connect the replot slot with this signal on two
9064 QCustomPlots to make them replot synchronously, it won't cause an infinite
9065 recursion.
9066
9067 \see replot, afterReplot
9068 */
9069
9070 /*! \fn void QCustomPlot::afterReplot()
9071
9072 This signal is emitted immediately after a replot has taken place (caused by a
9073 call to the slot \ref replot).
9074
9075 It is safe to mutually connect the replot slot with this signal on two
9076 QCustomPlots to make them replot synchronously, it won't cause an infinite
9077 recursion.
9078
9079 \see replot, beforeReplot
9080 */
9081
9082 /* end of documentation of signals */
9083 /* start of documentation of public members */
9084
9085 /*! \var QCPAxis *QCustomPlot::xAxis
9086
9087 A pointer to the primary x Axis (bottom) of the main axis rect of the plot.
9088
9089 QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis,
9090 \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy working
9091 with plots that only have a single axis rect and at most one axis at each axis
9092 rect side. If you use \link thelayoutsystem the layout system\endlink to add
9093 multiple axis rects or multiple axes to one side, use the \ref
9094 QCPAxisRect::axis interface to access the new axes. If one of the four default
9095 axes or the default legend is removed due to manipulation of the layout system
9096 (e.g. by removing the main axis rect), the corresponding pointers become 0.
9097 */
9098
9099 /*! \var QCPAxis *QCustomPlot::yAxis
9100
9101 A pointer to the primary y Axis (left) of the main axis rect of the plot.
9102
9103 QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis,
9104 \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy working
9105 with plots that only have a single axis rect and at most one axis at each axis
9106 rect side. If you use \link thelayoutsystem the layout system\endlink to add
9107 multiple axis rects or multiple axes to one side, use the \ref
9108 QCPAxisRect::axis interface to access the new axes. If one of the four default
9109 axes or the default legend is removed due to manipulation of the layout system
9110 (e.g. by removing the main axis rect), the corresponding pointers become 0.
9111 */
9112
9113 /*! \var QCPAxis *QCustomPlot::xAxis2
9114
9115 A pointer to the secondary x Axis (top) of the main axis rect of the plot.
9116 Secondary axes are invisible by default. Use QCPAxis::setVisible to change
9117 this (or use \ref QCPAxisRect::setupFullAxesBox).
9118
9119 QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis,
9120 \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy working
9121 with plots that only have a single axis rect and at most one axis at each axis
9122 rect side. If you use \link thelayoutsystem the layout system\endlink to add
9123 multiple axis rects or multiple axes to one side, use the \ref
9124 QCPAxisRect::axis interface to access the new axes. If one of the four default
9125 axes or the default legend is removed due to manipulation of the layout system
9126 (e.g. by removing the main axis rect), the corresponding pointers become 0.
9127 */
9128
9129 /*! \var QCPAxis *QCustomPlot::yAxis2
9130
9131 A pointer to the secondary y Axis (right) of the main axis rect of the plot.
9132 Secondary axes are invisible by default. Use QCPAxis::setVisible to change
9133 this (or use \ref QCPAxisRect::setupFullAxesBox).
9134
9135 QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis,
9136 \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy working
9137 with plots that only have a single axis rect and at most one axis at each axis
9138 rect side. If you use \link thelayoutsystem the layout system\endlink to add
9139 multiple axis rects or multiple axes to one side, use the \ref
9140 QCPAxisRect::axis interface to access the new axes. If one of the four default
9141 axes or the default legend is removed due to manipulation of the layout system
9142 (e.g. by removing the main axis rect), the corresponding pointers become 0.
9143 */
9144
9145 /*! \var QCPLegend *QCustomPlot::legend
9146
9147 A pointer to the default legend of the main axis rect. The legend is invisible
9148 by default. Use QCPLegend::setVisible to change this.
9149
9150 QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis,
9151 \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy working
9152 with plots that only have a single axis rect and at most one axis at each axis
9153 rect side. If you use \link thelayoutsystem the layout system\endlink to add
9154 multiple legends to the plot, use the layout system interface to access the
9155 new legend. For example, legends can be placed inside an axis rect's \ref
9156 QCPAxisRect::insetLayout "inset layout", and must then also be accessed via
9157 the inset layout. If the default legend is removed due to manipulation of the
9158 layout system (e.g. by removing the main axis rect), the corresponding pointer
9159 becomes 0.
9160 */
9161
9162 /* end of documentation of public members */
9163
9164 /*!
9165 Constructs a QCustomPlot and sets reasonable default values.
9166 */
9167 QCustomPlot::QCustomPlot(QWidget *parent)
9168 : QWidget(parent),
9169 xAxis(0),
9170 yAxis(0),
9171 xAxis2(0),
9172 yAxis2(0),
9173 legend(0),
9174 mPlotLayout(0),
9175 mAutoAddPlottableToLegend(true),
9176 mAntialiasedElements(QCP::aeNone),
9177 mNotAntialiasedElements(QCP::aeNone),
9178 mInteractions(0),
9179 mSelectionTolerance(8),
9180 mNoAntialiasingOnDrag(false),
9181 mBackgroundBrush(Qt::white, Qt::SolidPattern),
9182 mBackgroundScaled(true),
9183 mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding),
9184 mCurrentLayer(0),
9185 mPlottingHints(QCP::phCacheLabels | QCP::phForceRepaint),
9186 mMultiSelectModifier(Qt::ControlModifier),
9187 mPaintBuffer(size()),
9188 mMouseEventElement(0),
9189 mReplotting(false) {
9190 setAttribute(Qt::WA_NoMousePropagation);
9191 setAttribute(Qt::WA_OpaquePaintEvent);
9192 setMouseTracking(true);
9193 QLocale currentLocale = locale();
9194 currentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
9195 setLocale(currentLocale);
9196
9197 // create initial layers:
9198 mLayers.append(new QCPLayer(this, QLatin1String("background")));
9199 mLayers.append(new QCPLayer(this, QLatin1String("grid")));
9200 mLayers.append(new QCPLayer(this, QLatin1String("main")));
9201 mLayers.append(new QCPLayer(this, QLatin1String("axes")));
9202 mLayers.append(new QCPLayer(this, QLatin1String("legend")));
9203 updateLayerIndices();
9204 setCurrentLayer(QLatin1String("main"));
9205
9206 // create initial layout, axis rect and legend:
9207 mPlotLayout = new QCPLayoutGrid;
9208 mPlotLayout->initializeParentPlot(this);
9209 mPlotLayout->setParent(this); // important because if parent is QWidget,
9210 // QCPLayout::sizeConstraintsChanged will call
9211 // QWidget::updateGeometry
9212 mPlotLayout->setLayer(QLatin1String("main"));
9213 QCPAxisRect *defaultAxisRect = new QCPAxisRect(this, true);
9214 mPlotLayout->addElement(0, 0, defaultAxisRect);
9215 xAxis = defaultAxisRect->axis(QCPAxis::atBottom);
9216 yAxis = defaultAxisRect->axis(QCPAxis::atLeft);
9217 xAxis2 = defaultAxisRect->axis(QCPAxis::atTop);
9218 yAxis2 = defaultAxisRect->axis(QCPAxis::atRight);
9219 legend = new QCPLegend;
9220 legend->setVisible(false);
9221 defaultAxisRect->insetLayout()->addElement(legend,
9222 Qt::AlignRight | Qt::AlignTop);
9223 defaultAxisRect->insetLayout()->setMargins(QMargins(12, 12, 12, 12));
9224
9225 defaultAxisRect->setLayer(QLatin1String("background"));
9226 xAxis->setLayer(QLatin1String("axes"));
9227 yAxis->setLayer(QLatin1String("axes"));
9228 xAxis2->setLayer(QLatin1String("axes"));
9229 yAxis2->setLayer(QLatin1String("axes"));
9230 xAxis->grid()->setLayer(QLatin1String("grid"));
9231 yAxis->grid()->setLayer(QLatin1String("grid"));
9232 xAxis2->grid()->setLayer(QLatin1String("grid"));
9233 yAxis2->grid()->setLayer(QLatin1String("grid"));
9234 legend->setLayer(QLatin1String("legend"));
9235
9236 setViewport(rect()); // needs to be called after mPlotLayout has been created
9237
9238 replot();
9239 }
9240
9241 QCustomPlot::~QCustomPlot() {
9242 clearPlottables();
9243 clearItems();
9244
9245 if (mPlotLayout) {
9246 delete mPlotLayout;
9247 mPlotLayout = 0;
9248 }
9249
9250 mCurrentLayer = 0;
9251 qDeleteAll(mLayers); // don't use removeLayer, because it would prevent the
9252 // last layer to be removed
9253 mLayers.clear();
9254 }
9255
9256 /*!
9257 Sets which elements are forcibly drawn antialiased as an \a or combination of
9258 QCP::AntialiasedElement.
9259
9260 This overrides the antialiasing settings for whole element groups, normally
9261 controlled with the \a setAntialiasing function on the individual elements. If
9262 an element is neither specified in \ref setAntialiasedElements nor in \ref
9263 setNotAntialiasedElements, the antialiasing setting on each individual element
9264 instance is used.
9265
9266 For example, if \a antialiasedElements contains \ref QCP::aePlottables, all
9267 plottables will be drawn antialiased, no matter what the specific
9268 QCPAbstractPlottable::setAntialiased value was set to.
9269
9270 if an element in \a antialiasedElements is already set in \ref
9271 setNotAntialiasedElements, it is removed from there.
9272
9273 \see setNotAntialiasedElements
9274 */
9275 void QCustomPlot::setAntialiasedElements(
9276 const QCP::AntialiasedElements &antialiasedElements) {
9277 mAntialiasedElements = antialiasedElements;
9278
9279 // make sure elements aren't in mNotAntialiasedElements and
9280 // mAntialiasedElements simultaneously:
9281 if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
9282 mNotAntialiasedElements |= ~mAntialiasedElements;
9283 }
9284
9285 /*!
9286 Sets whether the specified \a antialiasedElement is forcibly drawn
9287 antialiased.
9288
9289 See \ref setAntialiasedElements for details.
9290
9291 \see setNotAntialiasedElement
9292 */
9293 void QCustomPlot::setAntialiasedElement(
9294 QCP::AntialiasedElement antialiasedElement, bool enabled) {
9295 if (!enabled && mAntialiasedElements.testFlag(antialiasedElement))
9296 mAntialiasedElements &= ~antialiasedElement;
9297 else if (enabled && !mAntialiasedElements.testFlag(antialiasedElement))
9298 mAntialiasedElements |= antialiasedElement;
9299
9300 // make sure elements aren't in mNotAntialiasedElements and
9301 // mAntialiasedElements simultaneously:
9302 if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
9303 mNotAntialiasedElements |= ~mAntialiasedElements;
9304 }
9305
9306 /*!
9307 Sets which elements are forcibly drawn not antialiased as an \a or combination
9308 of QCP::AntialiasedElement.
9309
9310 This overrides the antialiasing settings for whole element groups, normally
9311 controlled with the \a setAntialiasing function on the individual elements. If
9312 an element is neither specified in \ref setAntialiasedElements nor in \ref
9313 setNotAntialiasedElements, the antialiasing setting on each individual element
9314 instance is used.
9315
9316 For example, if \a notAntialiasedElements contains \ref QCP::aePlottables, no
9317 plottables will be drawn antialiased, no matter what the specific
9318 QCPAbstractPlottable::setAntialiased value was set to.
9319
9320 if an element in \a notAntialiasedElements is already set in \ref
9321 setAntialiasedElements, it is removed from there.
9322
9323 \see setAntialiasedElements
9324 */
9325 void QCustomPlot::setNotAntialiasedElements(
9326 const QCP::AntialiasedElements &notAntialiasedElements) {
9327 mNotAntialiasedElements = notAntialiasedElements;
9328
9329 // make sure elements aren't in mNotAntialiasedElements and
9330 // mAntialiasedElements simultaneously:
9331 if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
9332 mAntialiasedElements |= ~mNotAntialiasedElements;
9333 }
9334
9335 /*!
9336 Sets whether the specified \a notAntialiasedElement is forcibly drawn not
9337 antialiased.
9338
9339 See \ref setNotAntialiasedElements for details.
9340
9341 \see setAntialiasedElement
9342 */
9343 void QCustomPlot::setNotAntialiasedElement(
9344 QCP::AntialiasedElement notAntialiasedElement, bool enabled) {
9345 if (!enabled && mNotAntialiasedElements.testFlag(notAntialiasedElement))
9346 mNotAntialiasedElements &= ~notAntialiasedElement;
9347 else if (enabled && !mNotAntialiasedElements.testFlag(notAntialiasedElement))
9348 mNotAntialiasedElements |= notAntialiasedElement;
9349
9350 // make sure elements aren't in mNotAntialiasedElements and
9351 // mAntialiasedElements simultaneously:
9352 if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
9353 mAntialiasedElements |= ~mNotAntialiasedElements;
9354 }
9355
9356 /*!
9357 If set to true, adding a plottable (e.g. a graph) to the QCustomPlot
9358 automatically also adds the plottable to the legend (QCustomPlot::legend).
9359
9360 \see addPlottable, addGraph, QCPLegend::addItem
9361 */
9362 void QCustomPlot::setAutoAddPlottableToLegend(bool on) {
9363 mAutoAddPlottableToLegend = on;
9364 }
9365
9366 /*!
9367 Sets the possible interactions of this QCustomPlot as an or-combination of
9368 \ref QCP::Interaction enums. There are the following types of interactions:
9369
9370 <b>Axis range manipulation</b> is controlled via \ref QCP::iRangeDrag and \ref
9371 QCP::iRangeZoom. When the respective interaction is enabled, the user may drag
9372 axes ranges and zoom with the mouse wheel. For details how to control which
9373 axes the user may drag/zoom and in what orientations, see \ref
9374 QCPAxisRect::setRangeDrag, \ref QCPAxisRect::setRangeZoom, \ref
9375 QCPAxisRect::setRangeDragAxes, \ref QCPAxisRect::setRangeZoomAxes.
9376
9377 <b>Plottable selection</b> is controlled by \ref QCP::iSelectPlottables. If
9378 \ref QCP::iSelectPlottables is set, the user may select plottables (graphs,
9379 curves, bars,...) by clicking on them or in their vicinity (\ref
9380 setSelectionTolerance). Whether the user can actually select a plottable can
9381 further be restricted with the \ref QCPAbstractPlottable::setSelectable
9382 function on the specific plottable. To find out whether a specific plottable
9383 is selected, call QCPAbstractPlottable::selected(). To retrieve a list of all
9384 currently selected plottables, call \ref selectedPlottables. If you're only
9385 interested in QCPGraphs, you may use the convenience function \ref
9386 selectedGraphs.
9387
9388 <b>Item selection</b> is controlled by \ref QCP::iSelectItems. If \ref
9389 QCP::iSelectItems is set, the user may select items (QCPItemLine,
9390 QCPItemText,...) by clicking on them or in their vicinity. To find out whether
9391 a specific item is selected, call QCPAbstractItem::selected(). To retrieve a
9392 list of all currently selected items, call \ref selectedItems.
9393
9394 <b>Axis selection</b> is controlled with \ref QCP::iSelectAxes. If \ref
9395 QCP::iSelectAxes is set, the user may select parts of the axes by clicking on
9396 them. What parts exactly (e.g. Axis base line, tick labels, axis label) are
9397 selectable can be controlled via \ref QCPAxis::setSelectableParts for each
9398 axis. To retrieve a list of all axes that currently contain selected parts,
9399 call \ref selectedAxes. Which parts of an axis are selected, can be retrieved
9400 with QCPAxis::selectedParts().
9401
9402 <b>Legend selection</b> is controlled with \ref QCP::iSelectLegend. If this is
9403 set, the user may select the legend itself or individual items by clicking on
9404 them. What parts exactly are selectable can be controlled via \ref
9405 QCPLegend::setSelectableParts. To find out whether the legend or any of its
9406 child items are selected, check the value of QCPLegend::selectedParts. To find
9407 out which child items are selected, call \ref QCPLegend::selectedItems.
9408
9409 <b>All other selectable elements</b> The selection of all other selectable
9410 objects (e.g. QCPPlotTitle, or your own layerable subclasses) is controlled
9411 with \ref QCP::iSelectOther. If set, the user may select those objects by
9412 clicking on them. To find out which are currently selected, you need to check
9413 their selected state explicitly.
9414
9415 If the selection state has changed by user interaction, the \ref
9416 selectionChangedByUser signal is emitted. Each selectable object additionally
9417 emits an individual selectionChanged signal whenever their selection state has
9418 changed, i.e. not only by user interaction.
9419
9420 To allow multiple objects to be selected by holding the selection modifier
9421 (\ref setMultiSelectModifier), set the flag \ref QCP::iMultiSelect.
9422
9423 \note In addition to the selection mechanism presented here, QCustomPlot
9424 always emits corresponding signals, when an object is clicked or double
9425 clicked. see \ref plottableClick and \ref plottableDoubleClick for example.
9426
9427 \see setInteraction, setSelectionTolerance
9428 */
9429 void QCustomPlot::setInteractions(const QCP::Interactions &interactions) {
9430 mInteractions = interactions;
9431 }
9432
9433 /*!
9434 Sets the single \a interaction of this QCustomPlot to \a enabled.
9435
9436 For details about the interaction system, see \ref setInteractions.
9437
9438 \see setInteractions
9439 */
9440 void QCustomPlot::setInteraction(const QCP::Interaction &interaction,
9441 bool enabled) {
9442 if (!enabled && mInteractions.testFlag(interaction))
9443 mInteractions &= ~interaction;
9444 else if (enabled && !mInteractions.testFlag(interaction))
9445 mInteractions |= interaction;
9446 }
9447
9448 /*!
9449 Sets the tolerance that is used to decide whether a click selects an object
9450 (e.g. a plottable) or not.
9451
9452 If the user clicks in the vicinity of the line of e.g. a QCPGraph, it's only
9453 regarded as a potential selection when the minimum distance between the click
9454 position and the graph line is smaller than \a pixels. Objects that are
9455 defined by an area (e.g. QCPBars) only react to clicks directly inside the
9456 area and ignore this selection tolerance. In other words, it only has meaning
9457 for parts of objects that are too thin to exactly hit with a click and thus
9458 need such a tolerance.
9459
9460 \see setInteractions, QCPLayerable::selectTest
9461 */
9462 void QCustomPlot::setSelectionTolerance(int pixels) {
9463 mSelectionTolerance = pixels;
9464 }
9465
9466 /*!
9467 Sets whether antialiasing is disabled for this QCustomPlot while the user is
9468 dragging axes ranges. If many objects, especially plottables, are drawn
9469 antialiased, this greatly improves performance during dragging. Thus it
9470 creates a more responsive user experience. As soon as the user stops dragging,
9471 the last replot is done with normal antialiasing, to restore high image
9472 quality.
9473
9474 \see setAntialiasedElements, setNotAntialiasedElements
9475 */
9476 void QCustomPlot::setNoAntialiasingOnDrag(bool enabled) {
9477 mNoAntialiasingOnDrag = enabled;
9478 }
9479
9480 /*!
9481 Sets the plotting hints for this QCustomPlot instance as an \a or combination
9482 of QCP::PlottingHint.
9483
9484 \see setPlottingHint
9485 */
9486 void QCustomPlot::setPlottingHints(const QCP::PlottingHints &hints) {
9487 mPlottingHints = hints;
9488 }
9489
9490 /*!
9491 Sets the specified plotting \a hint to \a enabled.
9492
9493 \see setPlottingHints
9494 */
9495 void QCustomPlot::setPlottingHint(QCP::PlottingHint hint, bool enabled) {
9496 QCP::PlottingHints newHints = mPlottingHints;
9497 if (!enabled)
9498 newHints &= ~hint;
9499 else
9500 newHints |= hint;
9501
9502 if (newHints != mPlottingHints) setPlottingHints(newHints);
9503 }
9504
9505 /*!
9506 Sets the keyboard modifier that will be recognized as multi-select-modifier.
9507
9508 If \ref QCP::iMultiSelect is specified in \ref setInteractions, the user may
9509 select multiple objects by clicking on them one after the other while holding
9510 down \a modifier.
9511
9512 By default the multi-select-modifier is set to Qt::ControlModifier.
9513
9514 \see setInteractions
9515 */
9516 void QCustomPlot::setMultiSelectModifier(Qt::KeyboardModifier modifier) {
9517 mMultiSelectModifier = modifier;
9518 }
9519
9520 /*!
9521 Sets the viewport of this QCustomPlot. The Viewport is the area that the top
9522 level layout (QCustomPlot::plotLayout()) uses as its rect. Normally, the
9523 viewport is the entire widget rect.
9524
9525 This function is used to allow arbitrary size exports with \ref toPixmap, \ref
9526 savePng, \ref savePdf, etc. by temporarily changing the viewport size.
9527 */
9528 void QCustomPlot::setViewport(const QRect &rect) {
9529 mViewport = rect;
9530 if (mPlotLayout) mPlotLayout->setOuterRect(mViewport);
9531 }
9532
9533 /*!
9534 Sets \a pm as the viewport background pixmap (see \ref setViewport). The
9535 pixmap is always drawn below all other objects in the plot.
9536
9537 For cases where the provided pixmap doesn't have the same size as the
9538 viewport, scaling can be enabled with \ref setBackgroundScaled and the scaling
9539 mode (whether and how the aspect ratio is preserved) can be set with \ref
9540 setBackgroundScaledMode. To set all these options in one call, consider using
9541 the overloaded version of this function.
9542
9543 If a background brush was set with \ref setBackground(const QBrush &brush),
9544 the viewport will first be filled with that brush, before drawing the
9545 background pixmap. This can be useful for background pixmaps with translucent
9546 areas.
9547
9548 \see setBackgroundScaled, setBackgroundScaledMode
9549 */
9550 void QCustomPlot::setBackground(const QPixmap &pm) {
9551 mBackgroundPixmap = pm;
9552 mScaledBackgroundPixmap = QPixmap();
9553 }
9554
9555 /*!
9556 Sets the background brush of the viewport (see \ref setViewport).
9557
9558 Before drawing everything else, the background is filled with \a brush. If a
9559 background pixmap was set with \ref setBackground(const QPixmap &pm), this
9560 brush will be used to fill the viewport before the background pixmap is drawn.
9561 This can be useful for background pixmaps with translucent areas.
9562
9563 Set \a brush to Qt::NoBrush or Qt::Transparent to leave background
9564 transparent. This can be useful for exporting to image formats which support
9565 transparency, e.g. \ref savePng.
9566
9567 \see setBackgroundScaled, setBackgroundScaledMode
9568 */
9569 void QCustomPlot::setBackground(const QBrush &brush) {
9570 mBackgroundBrush = brush;
9571 }
9572
9573 /*! \overload
9574
9575 Allows setting the background pixmap of the viewport, whether it shall be
9576 scaled and how it shall be scaled in one call.
9577
9578 \see setBackground(const QPixmap &pm), setBackgroundScaled,
9579 setBackgroundScaledMode
9580 */
9581 void QCustomPlot::setBackground(const QPixmap &pm, bool scaled,
9582 Qt::AspectRatioMode mode) {
9583 mBackgroundPixmap = pm;
9584 mScaledBackgroundPixmap = QPixmap();
9585 mBackgroundScaled = scaled;
9586 mBackgroundScaledMode = mode;
9587 }
9588
9589 /*!
9590 Sets whether the viewport background pixmap shall be scaled to fit the
9591 viewport. If \a scaled is set to true, control whether and how the aspect
9592 ratio of the original pixmap is preserved with \ref setBackgroundScaledMode.
9593
9594 Note that the scaled version of the original pixmap is buffered, so there is
9595 no performance penalty on replots. (Except when the viewport dimensions are
9596 changed continuously.)
9597
9598 \see setBackground, setBackgroundScaledMode
9599 */
9600 void QCustomPlot::setBackgroundScaled(bool scaled) {
9601 mBackgroundScaled = scaled;
9602 }
9603
9604 /*!
9605 If scaling of the viewport background pixmap is enabled (\ref
9606 setBackgroundScaled), use this function to define whether and how the aspect
9607 ratio of the original pixmap is preserved.
9608
9609 \see setBackground, setBackgroundScaled
9610 */
9611 void QCustomPlot::setBackgroundScaledMode(Qt::AspectRatioMode mode) {
9612 mBackgroundScaledMode = mode;
9613 }
9614
9615 /*!
9616 Returns the plottable with \a index. If the index is invalid, returns 0.
9617
9618 There is an overloaded version of this function with no parameter which
9619 returns the last added plottable, see QCustomPlot::plottable()
9620
9621 \see plottableCount, addPlottable
9622 */
9623 QCPAbstractPlottable *QCustomPlot::plottable(int index) {
9624 if (index >= 0 && index < mPlottables.size()) {
9625 return mPlottables.at(index);
9626 } else {
9627 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
9628 return 0;
9629 }
9630 }
9631
9632 /*! \overload
9633
9634 Returns the last plottable that was added with \ref addPlottable. If there are
9635 no plottables in the plot, returns 0.
9636
9637 \see plottableCount, addPlottable
9638 */
9639 QCPAbstractPlottable *QCustomPlot::plottable() {
9640 if (!mPlottables.isEmpty()) {
9641 return mPlottables.last();
9642 } else
9643 return 0;
9644 }
9645
9646 /*!
9647 Adds the specified plottable to the plot and, if \ref
9648 setAutoAddPlottableToLegend is enabled, to the legend (QCustomPlot::legend).
9649 QCustomPlot takes ownership of the plottable.
9650
9651 Returns true on success, i.e. when \a plottable isn't already in the plot and
9652 the parent plot of \a plottable is this QCustomPlot (the latter is controlled
9653 by what axes were passed in the plottable's constructor).
9654
9655 \see plottable, plottableCount, removePlottable, clearPlottables
9656 */
9657 bool QCustomPlot::addPlottable(QCPAbstractPlottable *plottable) {
9658 if (mPlottables.contains(plottable)) {
9659 qDebug() << Q_FUNC_INFO << "plottable already added to this QCustomPlot:"
9660 << reinterpret_cast<quintptr>(plottable);
9661 return false;
9662 }
9663 if (plottable->parentPlot() != this) {
9664 qDebug() << Q_FUNC_INFO
9665 << "plottable not created with this QCustomPlot as parent:"
9666 << reinterpret_cast<quintptr>(plottable);
9667 return false;
9668 }
9669
9670 mPlottables.append(plottable);
9671 // possibly add plottable to legend:
9672 if (mAutoAddPlottableToLegend) plottable->addToLegend();
9673 // special handling for QCPGraphs to maintain the simple graph interface:
9674 if (QCPGraph *graph = qobject_cast<QCPGraph *>(plottable))
9675 mGraphs.append(graph);
9676 if (!plottable
9677 ->layer()) // usually the layer is already set in the constructor of
9678 // the plottable (via QCPLayerable constructor)
9679 plottable->setLayer(currentLayer());
9680 return true;
9681 }
9682
9683 /*!
9684 Removes the specified plottable from the plot and, if necessary, from the
9685 legend (QCustomPlot::legend).
9686
9687 Returns true on success.
9688
9689 \see addPlottable, clearPlottables
9690 */
9691 bool QCustomPlot::removePlottable(QCPAbstractPlottable *plottable) {
9692 if (!mPlottables.contains(plottable)) {
9693 qDebug() << Q_FUNC_INFO << "plottable not in list:"
9694 << reinterpret_cast<quintptr>(plottable);
9695 return false;
9696 }
9697
9698 // remove plottable from legend:
9699 plottable->removeFromLegend();
9700 // special handling for QCPGraphs to maintain the simple graph interface:
9701 if (QCPGraph *graph = qobject_cast<QCPGraph *>(plottable))
9702 mGraphs.removeOne(graph);
9703 // remove plottable:
9704 delete plottable;
9705 mPlottables.removeOne(plottable);
9706 return true;
9707 }
9708
9709 /*! \overload
9710
9711 Removes the plottable by its \a index.
9712 */
9713 bool QCustomPlot::removePlottable(int index) {
9714 if (index >= 0 && index < mPlottables.size())
9715 return removePlottable(mPlottables[index]);
9716 else {
9717 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
9718 return false;
9719 }
9720 }
9721
9722 /*!
9723 Removes all plottables from the plot (and the QCustomPlot::legend, if
9724 necessary).
9725
9726 Returns the number of plottables removed.
9727
9728 \see removePlottable
9729 */
9730 int QCustomPlot::clearPlottables() {
9731 int c = mPlottables.size();
9732 for (int i = c - 1; i >= 0; --i) removePlottable(mPlottables[i]);
9733 return c;
9734 }
9735
9736 /*!
9737 Returns the number of currently existing plottables in the plot
9738
9739 \see plottable, addPlottable
9740 */
9741 int QCustomPlot::plottableCount() const { return mPlottables.size(); }
9742
9743 /*!
9744 Returns a list of the selected plottables. If no plottables are currently
9745 selected, the list is empty.
9746
9747 There is a convenience function if you're only interested in selected graphs,
9748 see \ref selectedGraphs.
9749
9750 \see setInteractions, QCPAbstractPlottable::setSelectable,
9751 QCPAbstractPlottable::setSelected
9752 */
9753 QList<QCPAbstractPlottable *> QCustomPlot::selectedPlottables() const {
9754 QList<QCPAbstractPlottable *> result;
9755 foreach (QCPAbstractPlottable *plottable, mPlottables) {
9756 if (plottable->selected()) result.append(plottable);
9757 }
9758 return result;
9759 }
9760
9761 /*!
9762 Returns the plottable at the pixel position \a pos. Plottables that only
9763 consist of single lines (like graphs) have a tolerance band around them, see
9764 \ref setSelectionTolerance. If multiple plottables come into consideration,
9765 the one closest to \a pos is returned.
9766
9767 If \a onlySelectable is true, only plottables that are selectable
9768 (QCPAbstractPlottable::setSelectable) are considered.
9769
9770 If there is no plottable at \a pos, the return value is 0.
9771
9772 \see itemAt, layoutElementAt
9773 */
9774 QCPAbstractPlottable *QCustomPlot::plottableAt(const QPointF &pos,
9775 bool onlySelectable) const {
9776 QCPAbstractPlottable *resultPlottable = 0;
9777 double resultDistance =
9778 mSelectionTolerance; // only regard clicks with distances smaller than
9779 // mSelectionTolerance as selections, so initialize
9780 // with that value
9781
9782 foreach (QCPAbstractPlottable *plottable, mPlottables) {
9783 if (onlySelectable &&
9784 !plottable->selectable()) // we could have also passed onlySelectable
9785 // to the selectTest function, but checking
9786 // here is faster, because we have access to
9787 // QCPabstractPlottable::selectable
9788 continue;
9789 if ((plottable->keyAxis()->axisRect()->rect() &
9790 plottable->valueAxis()->axisRect()->rect())
9791 .contains(
9792 pos.toPoint())) // only consider clicks inside the rect that is
9793 // spanned by the plottable's key/value axes
9794 {
9795 double currentDistance = plottable->selectTest(pos, false);
9796 if (currentDistance >= 0 && currentDistance < resultDistance) {
9797 resultPlottable = plottable;
9798 resultDistance = currentDistance;
9799 }
9800 }
9801 }
9802
9803 return resultPlottable;
9804 }
9805
9806 /*!
9807 Returns whether this QCustomPlot instance contains the \a plottable.
9808
9809 \see addPlottable
9810 */
9811 bool QCustomPlot::hasPlottable(QCPAbstractPlottable *plottable) const {
9812 return mPlottables.contains(plottable);
9813 }
9814
9815 /*!
9816 Returns the graph with \a index. If the index is invalid, returns 0.
9817
9818 There is an overloaded version of this function with no parameter which
9819 returns the last created graph, see QCustomPlot::graph()
9820
9821 \see graphCount, addGraph
9822 */
9823 QCPGraph *QCustomPlot::graph(int index) const {
9824 if (index >= 0 && index < mGraphs.size()) {
9825 return mGraphs.at(index);
9826 } else {
9827 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
9828 return 0;
9829 }
9830 }
9831
9832 /*! \overload
9833
9834 Returns the last graph, that was created with \ref addGraph. If there are no
9835 graphs in the plot, returns 0.
9836
9837 \see graphCount, addGraph
9838 */
9839 QCPGraph *QCustomPlot::graph() const {
9840 if (!mGraphs.isEmpty()) {
9841 return mGraphs.last();
9842 } else
9843 return 0;
9844 }
9845
9846 /*!
9847 Creates a new graph inside the plot. If \a keyAxis and \a valueAxis are left
9848 unspecified (0), the bottom (xAxis) is used as key and the left (yAxis) is
9849 used as value axis. If specified, \a keyAxis and \a valueAxis must reside in
9850 this QCustomPlot.
9851
9852 \a keyAxis will be used as key axis (typically "x") and \a valueAxis as value
9853 axis (typically "y") for the graph.
9854
9855 Returns a pointer to the newly created graph, or 0 if adding the graph failed.
9856
9857 \see graph, graphCount, removeGraph, clearGraphs
9858 */
9859 QCPGraph *QCustomPlot::addGraph(QCPAxis *keyAxis, QCPAxis *valueAxis) {
9860 if (!keyAxis) keyAxis = xAxis;
9861 if (!valueAxis) valueAxis = yAxis;
9862 if (!keyAxis || !valueAxis) {
9863 qDebug() << Q_FUNC_INFO
9864 << "can't use default QCustomPlot xAxis or yAxis, because at "
9865 "least one is invalid (has been deleted)";
9866 return 0;
9867 }
9868 if (keyAxis->parentPlot() != this || valueAxis->parentPlot() != this) {
9869 qDebug() << Q_FUNC_INFO
9870 << "passed keyAxis or valueAxis doesn't have this QCustomPlot as "
9871 "parent";
9872 return 0;
9873 }
9874
9875 QCPGraph *newGraph = new QCPGraph(keyAxis, valueAxis);
9876 if (addPlottable(newGraph)) {
9877 newGraph->setName(QLatin1String("Graph ") +
9878 QString::number(mGraphs.size()));
9879 return newGraph;
9880 } else {
9881 delete newGraph;
9882 return 0;
9883 }
9884 }
9885
9886 /*!
9887 Removes the specified \a graph from the plot and, if necessary, from the
9888 QCustomPlot::legend. If any other graphs in the plot have a channel fill set
9889 towards the removed graph, the channel fill property of those graphs is reset
9890 to zero (no channel fill).
9891
9892 Returns true on success.
9893
9894 \see clearGraphs
9895 */
9896 bool QCustomPlot::removeGraph(QCPGraph *graph) {
9897 return removePlottable(graph);
9898 }
9899
9900 /*! \overload
9901
9902 Removes the graph by its \a index.
9903 */
9904 bool QCustomPlot::removeGraph(int index) {
9905 if (index >= 0 && index < mGraphs.size())
9906 return removeGraph(mGraphs[index]);
9907 else
9908 return false;
9909 }
9910
9911 /*!
9912 Removes all graphs from the plot (and the QCustomPlot::legend, if necessary).
9913
9914 Returns the number of graphs removed.
9915
9916 \see removeGraph
9917 */
9918 int QCustomPlot::clearGraphs() {
9919 int c = mGraphs.size();
9920 for (int i = c - 1; i >= 0; --i) removeGraph(mGraphs[i]);
9921 return c;
9922 }
9923
9924 /*!
9925 Returns the number of currently existing graphs in the plot
9926
9927 \see graph, addGraph
9928 */
9929 int QCustomPlot::graphCount() const { return mGraphs.size(); }
9930
9931 /*!
9932 Returns a list of the selected graphs. If no graphs are currently selected,
9933 the list is empty.
9934
9935 If you are not only interested in selected graphs but other plottables like
9936 QCPCurve, QCPBars, etc., use \ref selectedPlottables.
9937
9938 \see setInteractions, selectedPlottables, QCPAbstractPlottable::setSelectable,
9939 QCPAbstractPlottable::setSelected
9940 */
9941 QList<QCPGraph *> QCustomPlot::selectedGraphs() const {
9942 QList<QCPGraph *> result;
9943 foreach (QCPGraph *graph, mGraphs) {
9944 if (graph->selected()) result.append(graph);
9945 }
9946 return result;
9947 }
9948
9949 /*!
9950 Returns the item with \a index. If the index is invalid, returns 0.
9951
9952 There is an overloaded version of this function with no parameter which
9953 returns the last added item, see QCustomPlot::item()
9954
9955 \see itemCount, addItem
9956 */
9957 QCPAbstractItem *QCustomPlot::item(int index) const {
9958 if (index >= 0 && index < mItems.size()) {
9959 return mItems.at(index);
9960 } else {
9961 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
9962 return 0;
9963 }
9964 }
9965
9966 /*! \overload
9967
9968 Returns the last item, that was added with \ref addItem. If there are no items
9969 in the plot, returns 0.
9970
9971 \see itemCount, addItem
9972 */
9973 QCPAbstractItem *QCustomPlot::item() const {
9974 if (!mItems.isEmpty()) {
9975 return mItems.last();
9976 } else
9977 return 0;
9978 }
9979
9980 /*!
9981 Adds the specified item to the plot. QCustomPlot takes ownership of the item.
9982
9983 Returns true on success, i.e. when \a item wasn't already in the plot and the
9984 parent plot of \a item is this QCustomPlot.
9985
9986 \see item, itemCount, removeItem, clearItems
9987 */
9988 bool QCustomPlot::addItem(QCPAbstractItem *item) {
9989 if (!mItems.contains(item) && item->parentPlot() == this) {
9990 mItems.append(item);
9991 return true;
9992 } else {
9993 qDebug() << Q_FUNC_INFO
9994 << "item either already in list or not created with this "
9995 "QCustomPlot as parent:"
9996 << reinterpret_cast<quintptr>(item);
9997 return false;
9998 }
9999 }
10000
10001 /*!
10002 Removes the specified item from the plot.
10003
10004 Returns true on success.
10005
10006 \see addItem, clearItems
10007 */
10008 bool QCustomPlot::removeItem(QCPAbstractItem *item) {
10009 if (mItems.contains(item)) {
10010 delete item;
10011 mItems.removeOne(item);
10012 return true;
10013 } else {
10014 qDebug() << Q_FUNC_INFO
10015 << "item not in list:" << reinterpret_cast<quintptr>(item);
10016 return false;
10017 }
10018 }
10019
10020 /*! \overload
10021
10022 Removes the item by its \a index.
10023 */
10024 bool QCustomPlot::removeItem(int index) {
10025 if (index >= 0 && index < mItems.size())
10026 return removeItem(mItems[index]);
10027 else {
10028 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
10029 return false;
10030 }
10031 }
10032
10033 /*!
10034 Removes all items from the plot.
10035
10036 Returns the number of items removed.
10037
10038 \see removeItem
10039 */
10040 int QCustomPlot::clearItems() {
10041 int c = mItems.size();
10042 for (int i = c - 1; i >= 0; --i) removeItem(mItems[i]);
10043 return c;
10044 }
10045
10046 /*!
10047 Returns the number of currently existing items in the plot
10048
10049 \see item, addItem
10050 */
10051 int QCustomPlot::itemCount() const { return mItems.size(); }
10052
10053 /*!
10054 Returns a list of the selected items. If no items are currently selected, the
10055 list is empty.
10056
10057 \see setInteractions, QCPAbstractItem::setSelectable,
10058 QCPAbstractItem::setSelected
10059 */
10060 QList<QCPAbstractItem *> QCustomPlot::selectedItems() const {
10061 QList<QCPAbstractItem *> result;
10062 foreach (QCPAbstractItem *item, mItems) {
10063 if (item->selected()) result.append(item);
10064 }
10065 return result;
10066 }
10067
10068 /*!
10069 Returns the item at the pixel position \a pos. Items that only consist of
10070 single lines (e.g. \ref QCPItemLine or \ref QCPItemCurve) have a tolerance
10071 band around them, see \ref setSelectionTolerance. If multiple items come into
10072 consideration, the one closest to \a pos is returned.
10073
10074 If \a onlySelectable is true, only items that are selectable
10075 (QCPAbstractItem::setSelectable) are considered.
10076
10077 If there is no item at \a pos, the return value is 0.
10078
10079 \see plottableAt, layoutElementAt
10080 */
10081 QCPAbstractItem *QCustomPlot::itemAt(const QPointF &pos,
10082 bool onlySelectable) const {
10083 QCPAbstractItem *resultItem = 0;
10084 double resultDistance =
10085 mSelectionTolerance; // only regard clicks with distances smaller than
10086 // mSelectionTolerance as selections, so initialize
10087 // with that value
10088
10089 foreach (QCPAbstractItem *item, mItems) {
10090 if (onlySelectable &&
10091 !item->selectable()) // we could have also passed onlySelectable to the
10092 // selectTest function, but checking here is
10093 // faster, because we have access to
10094 // QCPAbstractItem::selectable
10095 continue;
10096 if (!item->clipToAxisRect() ||
10097 item->clipRect().contains(
10098 pos.toPoint())) // only consider clicks inside axis cliprect of the
10099 // item if actually clipped to it
10100 {
10101 double currentDistance = item->selectTest(pos, false);
10102 if (currentDistance >= 0 && currentDistance < resultDistance) {
10103 resultItem = item;
10104 resultDistance = currentDistance;
10105 }
10106 }
10107 }
10108
10109 return resultItem;
10110 }
10111
10112 /*!
10113 Returns whether this QCustomPlot contains the \a item.
10114
10115 \see addItem
10116 */
10117 bool QCustomPlot::hasItem(QCPAbstractItem *item) const {
10118 return mItems.contains(item);
10119 }
10120
10121 /*!
10122 Returns the layer with the specified \a name. If there is no layer with the
10123 specified name, 0 is returned.
10124
10125 Layer names are case-sensitive.
10126
10127 \see addLayer, moveLayer, removeLayer
10128 */
10129 QCPLayer *QCustomPlot::layer(const QString &name) const {
10130 foreach (QCPLayer *layer, mLayers) {
10131 if (layer->name() == name) return layer;
10132 }
10133 return 0;
10134 }
10135
10136 /*! \overload
10137
10138 Returns the layer by \a index. If the index is invalid, 0 is returned.
10139
10140 \see addLayer, moveLayer, removeLayer
10141 */
10142 QCPLayer *QCustomPlot::layer(int index) const {
10143 if (index >= 0 && index < mLayers.size()) {
10144 return mLayers.at(index);
10145 } else {
10146 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
10147 return 0;
10148 }
10149 }
10150
10151 /*!
10152 Returns the layer that is set as current layer (see \ref setCurrentLayer).
10153 */
10154 QCPLayer *QCustomPlot::currentLayer() const { return mCurrentLayer; }
10155
10156 /*!
10157 Sets the layer with the specified \a name to be the current layer. All
10158 layerables (\ref QCPLayerable), e.g. plottables and items, are created on the
10159 current layer.
10160
10161 Returns true on success, i.e. if there is a layer with the specified \a name
10162 in the QCustomPlot.
10163
10164 Layer names are case-sensitive.
10165
10166 \see addLayer, moveLayer, removeLayer, QCPLayerable::setLayer
10167 */
10168 bool QCustomPlot::setCurrentLayer(const QString &name) {
10169 if (QCPLayer *newCurrentLayer = layer(name)) {
10170 return setCurrentLayer(newCurrentLayer);
10171 } else {
10172 qDebug() << Q_FUNC_INFO << "layer with name doesn't exist:" << name;
10173 return false;
10174 }
10175 }
10176
10177 /*! \overload
10178
10179 Sets the provided \a layer to be the current layer.
10180
10181 Returns true on success, i.e. when \a layer is a valid layer in the
10182 QCustomPlot.
10183
10184 \see addLayer, moveLayer, removeLayer
10185 */
10186 bool QCustomPlot::setCurrentLayer(QCPLayer *layer) {
10187 if (!mLayers.contains(layer)) {
10188 qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:"
10189 << reinterpret_cast<quintptr>(layer);
10190 return false;
10191 }
10192
10193 mCurrentLayer = layer;
10194 return true;
10195 }
10196
10197 /*!
10198 Returns the number of currently existing layers in the plot
10199
10200 \see layer, addLayer
10201 */
10202 int QCustomPlot::layerCount() const { return mLayers.size(); }
10203
10204 /*!
10205 Adds a new layer to this QCustomPlot instance. The new layer will have the
10206 name \a name, which must be unique. Depending on \a insertMode, it is
10207 positioned either below or above \a otherLayer.
10208
10209 Returns true on success, i.e. if there is no other layer named \a name and \a
10210 otherLayer is a valid layer inside this QCustomPlot.
10211
10212 If \a otherLayer is 0, the highest layer in the QCustomPlot will be used.
10213
10214 For an explanation of what layers are in QCustomPlot, see the documentation of
10215 \ref QCPLayer.
10216
10217 \see layer, moveLayer, removeLayer
10218 */
10219 bool QCustomPlot::addLayer(const QString &name, QCPLayer *otherLayer,
10220 QCustomPlot::LayerInsertMode insertMode) {
10221 if (!otherLayer) otherLayer = mLayers.last();
10222 if (!mLayers.contains(otherLayer)) {
10223 qDebug() << Q_FUNC_INFO << "otherLayer not a layer of this QCustomPlot:"
10224 << reinterpret_cast<quintptr>(otherLayer);
10225 return false;
10226 }
10227 if (layer(name)) {
10228 qDebug() << Q_FUNC_INFO << "A layer exists already with the name" << name;
10229 return false;
10230 }
10231
10232 QCPLayer *newLayer = new QCPLayer(this, name);
10233 mLayers.insert(otherLayer->index() + (insertMode == limAbove ? 1 : 0),
10234 newLayer);
10235 updateLayerIndices();
10236 return true;
10237 }
10238
10239 /*!
10240 Removes the specified \a layer and returns true on success.
10241
10242 All layerables (e.g. plottables and items) on the removed layer will be moved
10243 to the layer below \a layer. If \a layer is the bottom layer, the layerables
10244 are moved to the layer above. In both cases, the total rendering order of all
10245 layerables in the QCustomPlot is preserved.
10246
10247 If \a layer is the current layer (\ref setCurrentLayer), the layer below (or
10248 above, if bottom layer) becomes the new current layer.
10249
10250 It is not possible to remove the last layer of the plot.
10251
10252 \see layer, addLayer, moveLayer
10253 */
10254 bool QCustomPlot::removeLayer(QCPLayer *layer) {
10255 if (!mLayers.contains(layer)) {
10256 qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:"
10257 << reinterpret_cast<quintptr>(layer);
10258 return false;
10259 }
10260 if (mLayers.size() < 2) {
10261 qDebug() << Q_FUNC_INFO << "can't remove last layer";
10262 return false;
10263 }
10264
10265 // append all children of this layer to layer below (if this is lowest layer,
10266 // prepend to layer above)
10267 int removedIndex = layer->index();
10268 bool isFirstLayer = removedIndex == 0;
10269 QCPLayer *targetLayer = isFirstLayer ? mLayers.at(removedIndex + 1)
10270 : mLayers.at(removedIndex - 1);
10271 QList<QCPLayerable *> children = layer->children();
10272 if (isFirstLayer) // prepend in reverse order (so order relative to each
10273 // other stays the same)
10274 {
10275 for (int i = children.size() - 1; i >= 0; --i)
10276 children.at(i)->moveToLayer(targetLayer, true);
10277 } else // append normally
10278 {
10279 for (int i = 0; i < children.size(); ++i)
10280 children.at(i)->moveToLayer(targetLayer, false);
10281 }
10282 // if removed layer is current layer, change current layer to layer
10283 // below/above:
10284 if (layer == mCurrentLayer) setCurrentLayer(targetLayer);
10285 // remove layer:
10286 delete layer;
10287 mLayers.removeOne(layer);
10288 updateLayerIndices();
10289 return true;
10290 }
10291
10292 /*!
10293 Moves the specified \a layer either above or below \a otherLayer. Whether it's
10294 placed above or below is controlled with \a insertMode.
10295
10296 Returns true on success, i.e. when both \a layer and \a otherLayer are valid
10297 layers in the QCustomPlot.
10298
10299 \see layer, addLayer, moveLayer
10300 */
10301 bool QCustomPlot::moveLayer(QCPLayer *layer, QCPLayer *otherLayer,
10302 QCustomPlot::LayerInsertMode insertMode) {
10303 if (!mLayers.contains(layer)) {
10304 qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:"
10305 << reinterpret_cast<quintptr>(layer);
10306 return false;
10307 }
10308 if (!mLayers.contains(otherLayer)) {
10309 qDebug() << Q_FUNC_INFO << "otherLayer not a layer of this QCustomPlot:"
10310 << reinterpret_cast<quintptr>(otherLayer);
10311 return false;
10312 }
10313
10314 if (layer->index() > otherLayer->index())
10315 mLayers.move(layer->index(),
10316 otherLayer->index() + (insertMode == limAbove ? 1 : 0));
10317 else if (layer->index() < otherLayer->index())
10318 mLayers.move(layer->index(),
10319 otherLayer->index() + (insertMode == limAbove ? 0 : -1));
10320
10321 updateLayerIndices();
10322 return true;
10323 }
10324
10325 /*!
10326 Returns the number of axis rects in the plot.
10327
10328 All axis rects can be accessed via QCustomPlot::axisRect().
10329
10330 Initially, only one axis rect exists in the plot.
10331
10332 \see axisRect, axisRects
10333 */
10334 int QCustomPlot::axisRectCount() const { return axisRects().size(); }
10335
10336 /*!
10337 Returns the axis rect with \a index.
10338
10339 Initially, only one axis rect (with index 0) exists in the plot. If multiple
10340 axis rects were added, all of them may be accessed with this function in a
10341 linear fashion (even when they are nested in a layout hierarchy or inside
10342 other axis rects via QCPAxisRect::insetLayout).
10343
10344 \see axisRectCount, axisRects
10345 */
10346 QCPAxisRect *QCustomPlot::axisRect(int index) const {
10347 const QList<QCPAxisRect *> rectList = axisRects();
10348 if (index >= 0 && index < rectList.size()) {
10349 return rectList.at(index);
10350 } else {
10351 qDebug() << Q_FUNC_INFO << "invalid axis rect index" << index;
10352 return 0;
10353 }
10354 }
10355
10356 /*!
10357 Returns all axis rects in the plot.
10358
10359 \see axisRectCount, axisRect
10360 */
10361 QList<QCPAxisRect *> QCustomPlot::axisRects() const {
10362 QList<QCPAxisRect *> result;
10363 QStack<QCPLayoutElement *> elementStack;
10364 if (mPlotLayout) elementStack.push(mPlotLayout);
10365
10366 while (!elementStack.isEmpty()) {
10367 foreach (QCPLayoutElement *element, elementStack.pop()->elements(false)) {
10368 if (element) {
10369 elementStack.push(element);
10370 if (QCPAxisRect *ar = qobject_cast<QCPAxisRect *>(element))
10371 result.append(ar);
10372 }
10373 }
10374 }
10375
10376 return result;
10377 }
10378
10379 /*!
10380 Returns the layout element at pixel position \a pos. If there is no element at
10381 that position, returns 0.
10382
10383 Only visible elements are used. If \ref QCPLayoutElement::setVisible on the
10384 element itself or on any of its parent elements is set to false, it will not
10385 be considered.
10386
10387 \see itemAt, plottableAt
10388 */
10389 QCPLayoutElement *QCustomPlot::layoutElementAt(const QPointF &pos) const {
10390 QCPLayoutElement *currentElement = mPlotLayout;
10391 bool searchSubElements = true;
10392 while (searchSubElements && currentElement) {
10393 searchSubElements = false;
10394 foreach (QCPLayoutElement *subElement, currentElement->elements(false)) {
10395 if (subElement && subElement->realVisibility() &&
10396 subElement->selectTest(pos, false) >= 0) {
10397 currentElement = subElement;
10398 searchSubElements = true;
10399 break;
10400 }
10401 }
10402 }
10403 return currentElement;
10404 }
10405
10406 /*!
10407 Returns the axes that currently have selected parts, i.e. whose selection
10408 state is not \ref QCPAxis::spNone.
10409
10410 \see selectedPlottables, selectedLegends, setInteractions,
10411 QCPAxis::setSelectedParts, QCPAxis::setSelectableParts
10412 */
10413 QList<QCPAxis *> QCustomPlot::selectedAxes() const {
10414 QList<QCPAxis *> result, allAxes;
10415 foreach (QCPAxisRect *rect, axisRects()) allAxes << rect->axes();
10416
10417 foreach (QCPAxis *axis, allAxes) {
10418 if (axis->selectedParts() != QCPAxis::spNone) result.append(axis);
10419 }
10420
10421 return result;
10422 }
10423
10424 /*!
10425 Returns the legends that currently have selected parts, i.e. whose selection
10426 state is not \ref QCPLegend::spNone.
10427
10428 \see selectedPlottables, selectedAxes, setInteractions,
10429 QCPLegend::setSelectedParts, QCPLegend::setSelectableParts,
10430 QCPLegend::selectedItems
10431 */
10432 QList<QCPLegend *> QCustomPlot::selectedLegends() const {
10433 QList<QCPLegend *> result;
10434
10435 QStack<QCPLayoutElement *> elementStack;
10436 if (mPlotLayout) elementStack.push(mPlotLayout);
10437
10438 while (!elementStack.isEmpty()) {
10439 foreach (QCPLayoutElement *subElement,
10440 elementStack.pop()->elements(false)) {
10441 if (subElement) {
10442 elementStack.push(subElement);
10443 if (QCPLegend *leg = qobject_cast<QCPLegend *>(subElement)) {
10444 if (leg->selectedParts() != QCPLegend::spNone) result.append(leg);
10445 }
10446 }
10447 }
10448 }
10449
10450 return result;
10451 }
10452
10453 /*!
10454 Deselects all layerables (plottables, items, axes, legends,...) of the
10455 QCustomPlot.
10456
10457 Since calling this function is not a user interaction, this does not emit the
10458 \ref selectionChangedByUser signal. The individual selectionChanged signals
10459 are emitted though, if the objects were previously selected.
10460
10461 \see setInteractions, selectedPlottables, selectedItems, selectedAxes,
10462 selectedLegends
10463 */
10464 void QCustomPlot::deselectAll() {
10465 foreach (QCPLayer *layer, mLayers) {
10466 foreach (QCPLayerable *layerable, layer->children())
10467 layerable->deselectEvent(0);
10468 }
10469 }
10470
10471 /*!
10472 Causes a complete replot into the internal buffer. Finally, update() is
10473 called, to redraw the buffer on the QCustomPlot widget surface. This is the
10474 method that must be called to make changes, for example on the axis ranges or
10475 data points of graphs, visible.
10476
10477 Under a few circumstances, QCustomPlot causes a replot by itself. Those are
10478 resize events of the QCustomPlot widget and user interactions (object
10479 selection and range dragging/zooming).
10480
10481 Before the replot happens, the signal \ref beforeReplot is emitted. After the
10482 replot, \ref afterReplot is emitted. It is safe to mutually connect the replot
10483 slot with any of those two signals on two QCustomPlots to make them replot
10484 synchronously, it won't cause an infinite recursion.
10485 */
10486 void QCustomPlot::replot(QCustomPlot::RefreshPriority refreshPriority) {
10487 if (mReplotting) // incase signals loop back to replot slot
10488 return;
10489 mReplotting = true;
10490 emit beforeReplot();
10491
10492 mPaintBuffer.fill(mBackgroundBrush.style() == Qt::SolidPattern
10493 ? mBackgroundBrush.color()
10494 : Qt::transparent);
10495 QCPPainter painter;
10496 painter.begin(&mPaintBuffer);
10497 if (painter.isActive()) {
10498 painter.setRenderHint(
10499 QPainter::HighQualityAntialiasing); // to make Antialiasing look good
10500 // if using the OpenGL
10501 // graphicssystem
10502 if (mBackgroundBrush.style() != Qt::SolidPattern &&
10503 mBackgroundBrush.style() != Qt::NoBrush)
10504 painter.fillRect(mViewport, mBackgroundBrush);
10505 draw(&painter);
10506 painter.end();
10507 if ((refreshPriority == rpHint &&
10508 mPlottingHints.testFlag(QCP::phForceRepaint)) ||
10509 refreshPriority == rpImmediate)
10510 repaint();
10511 else
10512 update();
10513 } else // might happen if QCustomPlot has width or height zero
10514 qDebug() << Q_FUNC_INFO
10515 << "Couldn't activate painter on buffer. This usually happens "
10516 "because QCustomPlot has width or height zero.";
10517
10518 emit afterReplot();
10519 mReplotting = false;
10520 }
10521
10522 /*!
10523 Rescales the axes such that all plottables (like graphs) in the plot are fully
10524 visible.
10525
10526 if \a onlyVisiblePlottables is set to true, only the plottables that have
10527 their visibility set to true (QCPLayerable::setVisible), will be used to
10528 rescale the axes.
10529
10530 \see QCPAbstractPlottable::rescaleAxes, QCPAxis::rescale
10531 */
10532 void QCustomPlot::rescaleAxes(bool onlyVisiblePlottables) {
10533 QList<QCPAxis *> allAxes;
10534 foreach (QCPAxisRect *rect, axisRects()) allAxes << rect->axes();
10535
10536 foreach (QCPAxis *axis, allAxes) axis->rescale(onlyVisiblePlottables);
10537 }
10538
10539 /*!
10540 Saves a PDF with the vectorized plot to the file \a fileName. The axis ratio
10541 as well as the scale of texts and lines will be derived from the specified \a
10542 width and \a height. This means, the output will look like the normal
10543 on-screen output of a QCustomPlot widget with the corresponding pixel width
10544 and height. If either \a width or \a height is zero, the exported image will
10545 have the same dimensions as the QCustomPlot widget currently has.
10546
10547 \a noCosmeticPen disables the use of cosmetic pens when drawing to the PDF
10548 file. Cosmetic pens are pens with numerical width 0, which are always drawn as
10549 a one pixel wide line, no matter what zoom factor is set in the PDF-Viewer.
10550 For more information about cosmetic pens, see the QPainter and QPen
10551 documentation.
10552
10553 The objects of the plot will appear in the current selection state. If you
10554 don't want any selected objects to be painted in their selected look, deselect
10555 everything with \ref deselectAll before calling this function.
10556
10557 Returns true on success.
10558
10559 \warning
10560 \li If you plan on editing the exported PDF file with a vector graphics editor
10561 like Inkscape, it is advised to set \a noCosmeticPen to true to avoid losing
10562 those cosmetic lines (which might be quite many, because cosmetic pens are the
10563 default for e.g. axes and tick marks). \li If calling this function inside the
10564 constructor of the parent of the QCustomPlot widget (i.e. the MainWindow
10565 constructor, if QCustomPlot is inside the MainWindow), always provide explicit
10566 non-zero widths and heights. If you leave \a width or \a height as 0
10567 (default), this function uses the current width and height of the QCustomPlot
10568 widget. However, in Qt, these aren't defined yet inside the constructor, so
10569 you would get an image that has strange widths/heights.
10570
10571 \a pdfCreator and \a pdfTitle may be used to set the according metadata fields
10572 in the resulting PDF file.
10573
10574 \note On Android systems, this method does nothing and issues an according
10575 qDebug warning message. This is also the case if for other reasons the define
10576 flag QT_NO_PRINTER is set.
10577
10578 \see savePng, saveBmp, saveJpg, saveRastered
10579 */
10580 bool QCustomPlot::savePdf(const QString &fileName, bool noCosmeticPen,
10581 int width, int height, const QString &pdfCreator,
10582 const QString &pdfTitle) {
10583 bool success = false;
10584 #ifdef QT_NO_PRINTER
10585 Q_UNUSED(fileName)
10586 Q_UNUSED(noCosmeticPen)
10587 Q_UNUSED(width)
10588 Q_UNUSED(height)
10589 Q_UNUSED(pdfCreator)
10590 Q_UNUSED(pdfTitle)
10591 qDebug() << Q_FUNC_INFO
10592 << "Qt was built without printer support (QT_NO_PRINTER). PDF not "
10593 "created.";
10594 #else
10595 int newWidth, newHeight;
10596 if (width == 0 || height == 0) {
10597 newWidth = this->width();
10598 newHeight = this->height();
10599 } else {
10600 newWidth = width;
10601 newHeight = height;
10602 }
10603
10604 QPrinter printer(QPrinter::ScreenResolution);
10605 printer.setOutputFileName(fileName);
10606 printer.setOutputFormat(QPrinter::PdfFormat);
10607 printer.setColorMode(QPrinter::Color);
10608 printer.printEngine()->setProperty(QPrintEngine::PPK_Creator, pdfCreator);
10609 printer.printEngine()->setProperty(QPrintEngine::PPK_DocumentName, pdfTitle);
10610 QRect oldViewport = viewport();
10611 setViewport(QRect(0, 0, newWidth, newHeight));
10612 #if QT_VERSION < QT_VERSION_CHECK(5, 3, 0)
10613 printer.setFullPage(true);
10614 printer.setPaperSize(viewport().size(), QPrinter::DevicePixel);
10615 #else
10616 QPageLayout pageLayout;
10617 pageLayout.setMode(QPageLayout::FullPageMode);
10618 pageLayout.setOrientation(QPageLayout::Portrait);
10619 pageLayout.setMargins(QMarginsF(0, 0, 0, 0));
10620 pageLayout.setPageSize(QPageSize(viewport().size(), QPageSize::Point,
10621 QString(), QPageSize::ExactMatch));
10622 printer.setPageLayout(pageLayout);
10623 #endif
10624 QCPPainter printpainter;
10625 if (printpainter.begin(&printer)) {
10626 printpainter.setMode(QCPPainter::pmVectorized);
10627 printpainter.setMode(QCPPainter::pmNoCaching);
10628 printpainter.setMode(QCPPainter::pmNonCosmetic, noCosmeticPen);
10629 printpainter.setWindow(mViewport);
10630 if (mBackgroundBrush.style() != Qt::NoBrush &&
10631 mBackgroundBrush.color() != Qt::white &&
10632 mBackgroundBrush.color() != Qt::transparent &&
10633 mBackgroundBrush.color().alpha() >
10634 0) // draw pdf background color if not white/transparent
10635 printpainter.fillRect(viewport(), mBackgroundBrush);
10636 draw(&printpainter);
10637 printpainter.end();
10638 success = true;
10639 }
10640 setViewport(oldViewport);
10641 #endif // QT_NO_PRINTER
10642 return success;
10643 }
10644
10645 /*!
10646 Saves a PNG image file to \a fileName on disc. The output plot will have the
10647 dimensions \a width and \a height in pixels. If either \a width or \a height
10648 is zero, the exported image will have the same dimensions as the QCustomPlot
10649 widget currently has. Line widths and texts etc. are not scaled up when larger
10650 widths/heights are used. If you want that effect, use the \a scale parameter.
10651
10652 For example, if you set both \a width and \a height to 100 and \a scale to 2,
10653 you will end up with an image file of size 200*200 in which all graphical
10654 elements are scaled up by factor 2 (line widths, texts, etc.). This scaling is
10655 not done by stretching a 100*100 image, the result will have full 200*200
10656 pixel resolution.
10657
10658 If you use a high scaling factor, it is recommended to enable antialiasing for
10659 all elements via temporarily setting \ref QCustomPlot::setAntialiasedElements
10660 to \ref QCP::aeAll as this allows QCustomPlot to place objects with sub-pixel
10661 accuracy.
10662
10663 \warning If calling this function inside the constructor of the parent of the
10664 QCustomPlot widget (i.e. the MainWindow constructor, if QCustomPlot is inside
10665 the MainWindow), always provide explicit non-zero widths and heights. If you
10666 leave \a width or \a height as 0 (default), this function uses the current
10667 width and height of the QCustomPlot widget. However, in Qt, these aren't
10668 defined yet inside the constructor, so you would get an image that has strange
10669 widths/heights.
10670
10671 The objects of the plot will appear in the current selection state. If you
10672 don't want any selected objects to be painted in their selected look, deselect
10673 everything with \ref deselectAll before calling this function.
10674
10675 If you want the PNG to have a transparent background, call \ref
10676 setBackground(const QBrush &brush) with no brush (Qt::NoBrush) or a
10677 transparent color (Qt::transparent), before saving.
10678
10679 PNG compression can be controlled with the \a quality parameter which must be
10680 between 0 and 100 or -1 to use the default setting.
10681
10682 Returns true on success. If this function fails, most likely the PNG format
10683 isn't supported by the system, see Qt docs about
10684 QImageWriter::supportedImageFormats().
10685
10686 \see savePdf, saveBmp, saveJpg, saveRastered
10687 */
10688 bool QCustomPlot::savePng(const QString &fileName, int width, int height,
10689 double scale, int quality) {
10690 return saveRastered(fileName, width, height, scale, "PNG", quality);
10691 }
10692
10693 /*!
10694 Saves a JPG image file to \a fileName on disc. The output plot will have the
10695 dimensions \a width and \a height in pixels. If either \a width or \a height
10696 is zero, the exported image will have the same dimensions as the QCustomPlot
10697 widget currently has. Line widths and texts etc. are not scaled up when larger
10698 widths/heights are used. If you want that effect, use the \a scale parameter.
10699
10700 For example, if you set both \a width and \a height to 100 and \a scale to 2,
10701 you will end up with an image file of size 200*200 in which all graphical
10702 elements are scaled up by factor 2 (line widths, texts, etc.). This scaling is
10703 not done by stretching a 100*100 image, the result will have full 200*200
10704 pixel resolution.
10705
10706 If you use a high scaling factor, it is recommended to enable antialiasing for
10707 all elements via temporarily setting \ref QCustomPlot::setAntialiasedElements
10708 to \ref QCP::aeAll as this allows QCustomPlot to place objects with sub-pixel
10709 accuracy.
10710
10711 \warning If calling this function inside the constructor of the parent of the
10712 QCustomPlot widget (i.e. the MainWindow constructor, if QCustomPlot is inside
10713 the MainWindow), always provide explicit non-zero widths and heights. If you
10714 leave \a width or \a height as 0 (default), this function uses the current
10715 width and height of the QCustomPlot widget. However, in Qt, these aren't
10716 defined yet inside the constructor, so you would get an image that has strange
10717 widths/heights.
10718
10719 The objects of the plot will appear in the current selection state. If you
10720 don't want any selected objects to be painted in their selected look, deselect
10721 everything with \ref deselectAll before calling this function.
10722
10723 JPG compression can be controlled with the \a quality parameter which must be
10724 between 0 and 100 or -1 to use the default setting.
10725
10726 Returns true on success. If this function fails, most likely the JPG format
10727 isn't supported by the system, see Qt docs about
10728 QImageWriter::supportedImageFormats().
10729
10730 \see savePdf, savePng, saveBmp, saveRastered
10731 */
10732 bool QCustomPlot::saveJpg(const QString &fileName, int width, int height,
10733 double scale, int quality) {
10734 return saveRastered(fileName, width, height, scale, "JPG", quality);
10735 }
10736
10737 /*!
10738 Saves a BMP image file to \a fileName on disc. The output plot will have the
10739 dimensions \a width and \a height in pixels. If either \a width or \a height
10740 is zero, the exported image will have the same dimensions as the QCustomPlot
10741 widget currently has. Line widths and texts etc. are not scaled up when larger
10742 widths/heights are used. If you want that effect, use the \a scale parameter.
10743
10744 For example, if you set both \a width and \a height to 100 and \a scale to 2,
10745 you will end up with an image file of size 200*200 in which all graphical
10746 elements are scaled up by factor 2 (line widths, texts, etc.). This scaling is
10747 not done by stretching a 100*100 image, the result will have full 200*200
10748 pixel resolution.
10749
10750 If you use a high scaling factor, it is recommended to enable antialiasing for
10751 all elements via temporarily setting \ref QCustomPlot::setAntialiasedElements
10752 to \ref QCP::aeAll as this allows QCustomPlot to place objects with sub-pixel
10753 accuracy.
10754
10755 \warning If calling this function inside the constructor of the parent of the
10756 QCustomPlot widget (i.e. the MainWindow constructor, if QCustomPlot is inside
10757 the MainWindow), always provide explicit non-zero widths and heights. If you
10758 leave \a width or \a height as 0 (default), this function uses the current
10759 width and height of the QCustomPlot widget. However, in Qt, these aren't
10760 defined yet inside the constructor, so you would get an image that has strange
10761 widths/heights.
10762
10763 The objects of the plot will appear in the current selection state. If you
10764 don't want any selected objects to be painted in their selected look, deselect
10765 everything with \ref deselectAll before calling this function.
10766
10767 Returns true on success. If this function fails, most likely the BMP format
10768 isn't supported by the system, see Qt docs about
10769 QImageWriter::supportedImageFormats().
10770
10771 \see savePdf, savePng, saveJpg, saveRastered
10772 */
10773 bool QCustomPlot::saveBmp(const QString &fileName, int width, int height,
10774 double scale) {
10775 return saveRastered(fileName, width, height, scale, "BMP");
10776 }
10777
10778 /*! \internal
10779
10780 Returns a minimum size hint that corresponds to the minimum size of the top
10781 level layout
10782 (\ref plotLayout). To prevent QCustomPlot from being collapsed to size/width
10783 zero, set a minimum size (setMinimumSize) either on the whole QCustomPlot or
10784 on any layout elements inside the plot. This is especially important, when
10785 placed in a QLayout where other components try to take in as much space as
10786 possible (e.g. QMdiArea).
10787 */
10788 QSize QCustomPlot::minimumSizeHint() const {
10789 return mPlotLayout->minimumSizeHint();
10790 }
10791
10792 /*! \internal
10793
10794 Returns a size hint that is the same as \ref minimumSizeHint.
10795
10796 */
10797 QSize QCustomPlot::sizeHint() const { return mPlotLayout->minimumSizeHint(); }
10798
10799 /*! \internal
10800
10801 Event handler for when the QCustomPlot widget needs repainting. This does not
10802 cause a \ref replot, but draws the internal buffer on the widget surface.
10803 */
10804 void QCustomPlot::paintEvent(QPaintEvent *event) {
10805 Q_UNUSED(event);
10806 QPainter painter(this);
10807 painter.drawPixmap(0, 0, mPaintBuffer);
10808 }
10809
10810 /*! \internal
10811
10812 Event handler for a resize of the QCustomPlot widget. Causes the internal
10813 buffer to be resized to the new size. The viewport (which becomes the outer
10814 rect of mPlotLayout) is resized appropriately. Finally a \ref replot is
10815 performed.
10816 */
10817 void QCustomPlot::resizeEvent(QResizeEvent *event) {
10818 // resize and repaint the buffer:
10819 mPaintBuffer = QPixmap(event->size());
10820 setViewport(rect());
10821 replot(rpQueued); // queued update is important here, to prevent painting
10822 // issues in some contexts
10823 }
10824
10825 /*! \internal
10826
10827 Event handler for when a double click occurs. Emits the \ref mouseDoubleClick
10828 signal, then emits the specialized signals when certain objecs are clicked
10829 (e.g. \ref plottableDoubleClick, \ref axisDoubleClick, etc.). Finally
10830 determines the affected layout element and forwards the event to it.
10831
10832 \see mousePressEvent, mouseReleaseEvent
10833 */
10834 void QCustomPlot::mouseDoubleClickEvent(QMouseEvent *event) {
10835 emit mouseDoubleClick(event);
10836
10837 QVariant details;
10838 QCPLayerable *clickedLayerable = layerableAt(event->pos(), false, &details);
10839
10840 // emit specialized object double click signals:
10841 if (QCPAbstractPlottable *ap =
10842 qobject_cast<QCPAbstractPlottable *>(clickedLayerable))
10843 emit plottableDoubleClick(ap, event);
10844 else if (QCPAxis *ax = qobject_cast<QCPAxis *>(clickedLayerable))
10845 emit axisDoubleClick(ax, details.value<QCPAxis::SelectablePart>(), event);
10846 else if (QCPAbstractItem *ai =
10847 qobject_cast<QCPAbstractItem *>(clickedLayerable))
10848 emit itemDoubleClick(ai, event);
10849 else if (QCPLegend *lg = qobject_cast<QCPLegend *>(clickedLayerable))
10850 emit legendDoubleClick(lg, 0, event);
10851 else if (QCPAbstractLegendItem *li =
10852 qobject_cast<QCPAbstractLegendItem *>(clickedLayerable))
10853 emit legendDoubleClick(li->parentLegend(), li, event);
10854 else if (QCPPlotTitle *pt = qobject_cast<QCPPlotTitle *>(clickedLayerable))
10855 emit titleDoubleClick(event, pt);
10856
10857 // call double click event of affected layout element:
10858 if (QCPLayoutElement *el = layoutElementAt(event->pos()))
10859 el->mouseDoubleClickEvent(event);
10860
10861 // call release event of affected layout element (as in mouseReleaseEvent,
10862 // since the mouseDoubleClick replaces the second release event in double
10863 // click case):
10864 if (mMouseEventElement) {
10865 mMouseEventElement->mouseReleaseEvent(event);
10866 mMouseEventElement = 0;
10867 }
10868
10869 // QWidget::mouseDoubleClickEvent(event); don't call base class implementation
10870 // because it would just cause a mousePress/ReleaseEvent, which we don't want.
10871 }
10872
10873 /*! \internal
10874
10875 Event handler for when a mouse button is pressed. Emits the mousePress signal.
10876 Then determines the affected layout element and forwards the event to it.
10877
10878 \see mouseMoveEvent, mouseReleaseEvent
10879 */
10880 void QCustomPlot::mousePressEvent(QMouseEvent *event) {
10881 emit mousePress(event);
10882 mMousePressPos =
10883 event->pos(); // need this to determine in releaseEvent whether it was a
10884 // click (no position change between press and release)
10885
10886 // call event of affected layout element:
10887 mMouseEventElement = layoutElementAt(event->pos());
10888 if (mMouseEventElement) mMouseEventElement->mousePressEvent(event);
10889
10890 QWidget::mousePressEvent(event);
10891 }
10892
10893 /*! \internal
10894
10895 Event handler for when the cursor is moved. Emits the \ref mouseMove signal.
10896
10897 If a layout element has mouse capture focus (a mousePressEvent happened on top
10898 of the layout element before), the mouseMoveEvent is forwarded to that
10899 element.
10900
10901 \see mousePressEvent, mouseReleaseEvent
10902 */
10903 void QCustomPlot::mouseMoveEvent(QMouseEvent *event) {
10904 emit mouseMove(event);
10905
10906 // call event of affected layout element:
10907 if (mMouseEventElement) mMouseEventElement->mouseMoveEvent(event);
10908
10909 QWidget::mouseMoveEvent(event);
10910 }
10911
10912 /*! \internal
10913
10914 Event handler for when a mouse button is released. Emits the \ref mouseRelease
10915 signal.
10916
10917 If the mouse was moved less than a certain threshold in any direction since
10918 the \ref mousePressEvent, it is considered a click which causes the selection
10919 mechanism (if activated via \ref setInteractions) to possibly change selection
10920 states accordingly. Further, specialized mouse click signals are emitted (e.g.
10921 \ref plottableClick, \ref axisClick, etc.)
10922
10923 If a layout element has mouse capture focus (a \ref mousePressEvent happened
10924 on top of the layout element before), the \ref mouseReleaseEvent is forwarded
10925 to that element.
10926
10927 \see mousePressEvent, mouseMoveEvent
10928 */
10929 void QCustomPlot::mouseReleaseEvent(QMouseEvent *event) {
10930 emit mouseRelease(event);
10931 bool doReplot = false;
10932
10933 if ((mMousePressPos - event->pos()).manhattanLength() <
10934 5) // determine whether it was a click operation
10935 {
10936 if (event->button() == Qt::LeftButton) {
10937 // handle selection mechanism:
10938 QVariant details;
10939 QCPLayerable *clickedLayerable =
10940 layerableAt(event->pos(), true, &details);
10941 bool selectionStateChanged = false;
10942 bool additive = mInteractions.testFlag(QCP::iMultiSelect) &&
10943 event->modifiers().testFlag(mMultiSelectModifier);
10944 // deselect all other layerables if not additive selection:
10945 if (!additive) {
10946 foreach (QCPLayer *layer, mLayers) {
10947 foreach (QCPLayerable *layerable, layer->children()) {
10948 if (layerable != clickedLayerable &&
10949 mInteractions.testFlag(layerable->selectionCategory())) {
10950 bool selChanged = false;
10951 layerable->deselectEvent(&selChanged);
10952 selectionStateChanged |= selChanged;
10953 }
10954 }
10955 }
10956 }
10957 if (clickedLayerable &&
10958 mInteractions.testFlag(clickedLayerable->selectionCategory())) {
10959 // a layerable was actually clicked, call its selectEvent:
10960 bool selChanged = false;
10961 clickedLayerable->selectEvent(event, additive, details, &selChanged);
10962 selectionStateChanged |= selChanged;
10963 }
10964 if (selectionStateChanged) {
10965 doReplot = true;
10966 emit selectionChangedByUser();
10967 }
10968 }
10969
10970 // emit specialized object click signals:
10971 QVariant details;
10972 QCPLayerable *clickedLayerable = layerableAt(
10973 event->pos(), false,
10974 &details); // for these signals, selectability is ignored, that's why
10975 // we call this again with onlySelectable set to false
10976 if (QCPAbstractPlottable *ap =
10977 qobject_cast<QCPAbstractPlottable *>(clickedLayerable))
10978 emit plottableClick(ap, event);
10979 else if (QCPAxis *ax = qobject_cast<QCPAxis *>(clickedLayerable))
10980 emit axisClick(ax, details.value<QCPAxis::SelectablePart>(), event);
10981 else if (QCPAbstractItem *ai =
10982 qobject_cast<QCPAbstractItem *>(clickedLayerable))
10983 emit itemClick(ai, event);
10984 else if (QCPLegend *lg = qobject_cast<QCPLegend *>(clickedLayerable))
10985 emit legendClick(lg, 0, event);
10986 else if (QCPAbstractLegendItem *li =
10987 qobject_cast<QCPAbstractLegendItem *>(clickedLayerable))
10988 emit legendClick(li->parentLegend(), li, event);
10989 else if (QCPPlotTitle *pt = qobject_cast<QCPPlotTitle *>(clickedLayerable))
10990 emit titleClick(event, pt);
10991 }
10992
10993 // call event of affected layout element:
10994 if (mMouseEventElement) {
10995 mMouseEventElement->mouseReleaseEvent(event);
10996 mMouseEventElement = 0;
10997 }
10998
10999 if (doReplot || noAntialiasingOnDrag()) replot();
11000
11001 QWidget::mouseReleaseEvent(event);
11002 }
11003
11004 /*! \internal
11005
11006 Event handler for mouse wheel events. First, the \ref mouseWheel signal is
11007 emitted. Then determines the affected layout element and forwards the event to
11008 it.
11009
11010 */
11011 void QCustomPlot::wheelEvent(QWheelEvent *event) {
11012 emit mouseWheel(event);
11013
11014 // call event of affected layout element:
11015 if (QCPLayoutElement *el = layoutElementAt(event->pos()))
11016 el->wheelEvent(event);
11017
11018 QWidget::wheelEvent(event);
11019 }
11020
11021 /*! \internal
11022
11023 This is the main draw function. It draws the entire plot, including background
11024 pixmap, with the specified \a painter. Note that it does not fill the
11025 background with the background brush (as the user may specify with \ref
11026 setBackground(const QBrush &brush)), this is up to the respective functions
11027 calling this method (e.g. \ref replot, \ref toPixmap and \ref toPainter).
11028 */
11029 void QCustomPlot::draw(QCPPainter *painter) {
11030 // run through layout phases:
11031 mPlotLayout->update(QCPLayoutElement::upPreparation);
11032 mPlotLayout->update(QCPLayoutElement::upMargins);
11033 mPlotLayout->update(QCPLayoutElement::upLayout);
11034
11035 // draw viewport background pixmap:
11036 drawBackground(painter);
11037
11038 // draw all layered objects (grid, axes, plottables, items, legend,...):
11039 foreach (QCPLayer *layer, mLayers) {
11040 foreach (QCPLayerable *child, layer->children()) {
11041 if (child->realVisibility()) {
11042 painter->save();
11043 painter->setClipRect(child->clipRect().translated(0, -1));
11044 child->applyDefaultAntialiasingHint(painter);
11045 child->draw(painter);
11046 painter->restore();
11047 }
11048 }
11049 }
11050
11051 /* Debug code to draw all layout element rects
11052 foreach (QCPLayoutElement* el, findChildren<QCPLayoutElement*>())
11053 {
11054 painter->setBrush(Qt::NoBrush);
11055 painter->setPen(QPen(QColor(0, 0, 0, 100), 0, Qt::DashLine));
11056 painter->drawRect(el->rect());
11057 painter->setPen(QPen(QColor(255, 0, 0, 100), 0, Qt::DashLine));
11058 painter->drawRect(el->outerRect());
11059 }
11060 */
11061 }
11062
11063 /*! \internal
11064
11065 Draws the viewport background pixmap of the plot.
11066
11067 If a pixmap was provided via \ref setBackground, this function buffers the
11068 scaled version depending on \ref setBackgroundScaled and \ref
11069 setBackgroundScaledMode and then draws it inside the viewport with the
11070 provided \a painter. The scaled version is buffered in mScaledBackgroundPixmap
11071 to prevent expensive rescaling at every redraw. It is only updated, when the
11072 axis rect has changed in a way that requires a rescale of the background
11073 pixmap (this is dependent on the \ref setBackgroundScaledMode), or when a
11074 differend axis background pixmap was set.
11075
11076 Note that this function does not draw a fill with the background brush (\ref
11077 setBackground(const QBrush &brush)) beneath the pixmap.
11078
11079 \see setBackground, setBackgroundScaled, setBackgroundScaledMode
11080 */
11081 void QCustomPlot::drawBackground(QCPPainter *painter) {
11082 // Note: background color is handled in individual replot/save functions
11083
11084 // draw background pixmap (on top of fill, if brush specified):
11085 if (!mBackgroundPixmap.isNull()) {
11086 if (mBackgroundScaled) {
11087 // check whether mScaledBackground needs to be updated:
11088 QSize scaledSize(mBackgroundPixmap.size());
11089 scaledSize.scale(mViewport.size(), mBackgroundScaledMode);
11090 if (mScaledBackgroundPixmap.size() != scaledSize)
11091 mScaledBackgroundPixmap = mBackgroundPixmap.scaled(
11092 mViewport.size(), mBackgroundScaledMode, Qt::SmoothTransformation);
11093 painter->drawPixmap(mViewport.topLeft(), mScaledBackgroundPixmap,
11094 QRect(0, 0, mViewport.width(), mViewport.height()) &
11095 mScaledBackgroundPixmap.rect());
11096 } else {
11097 painter->drawPixmap(mViewport.topLeft(), mBackgroundPixmap,
11098 QRect(0, 0, mViewport.width(), mViewport.height()));
11099 }
11100 }
11101 }
11102
11103 /*! \internal
11104
11105 This method is used by \ref QCPAxisRect::removeAxis to report removed axes to
11106 the QCustomPlot so it may clear its QCustomPlot::xAxis, yAxis, xAxis2 and
11107 yAxis2 members accordingly.
11108 */
11109 void QCustomPlot::axisRemoved(QCPAxis *axis) {
11110 if (xAxis == axis) xAxis = 0;
11111 if (xAxis2 == axis) xAxis2 = 0;
11112 if (yAxis == axis) yAxis = 0;
11113 if (yAxis2 == axis) yAxis2 = 0;
11114
11115 // Note: No need to take care of range drag axes and range zoom axes, because
11116 // they are stored in smart pointers
11117 }
11118
11119 /*! \internal
11120
11121 This method is used by the QCPLegend destructor to report legend removal to
11122 the QCustomPlot so it may clear its QCustomPlot::legend member accordingly.
11123 */
11124 void QCustomPlot::legendRemoved(QCPLegend *legend) {
11125 if (this->legend == legend) this->legend = 0;
11126 }
11127
11128 /*! \internal
11129
11130 Assigns all layers their index (QCPLayer::mIndex) in the mLayers list. This
11131 method is thus called after every operation that changes the layer indices,
11132 like layer removal, layer creation, layer moving.
11133 */
11134 void QCustomPlot::updateLayerIndices() const {
11135 for (int i = 0; i < mLayers.size(); ++i) mLayers.at(i)->mIndex = i;
11136 }
11137
11138 /*! \internal
11139
11140 Returns the layerable at pixel position \a pos. If \a onlySelectable is set to
11141 true, only those layerables that are selectable will be considered. (Layerable
11142 subclasses communicate their selectability via the QCPLayerable::selectTest
11143 method, by returning -1.)
11144
11145 \a selectionDetails is an output parameter that contains selection specifics
11146 of the affected layerable. This is useful if the respective layerable shall be
11147 given a subsequent QCPLayerable::selectEvent (like in \ref mouseReleaseEvent).
11148 \a selectionDetails usually contains information about which part of the
11149 layerable was hit, in multi-part layerables (e.g. QCPAxis::SelectablePart).
11150 */
11151 QCPLayerable *QCustomPlot::layerableAt(const QPointF &pos, bool onlySelectable,
11152 QVariant *selectionDetails) const {
11153 for (int layerIndex = mLayers.size() - 1; layerIndex >= 0; --layerIndex) {
11154 const QList<QCPLayerable *> layerables = mLayers.at(layerIndex)->children();
11155 double minimumDistance = selectionTolerance() * 1.1;
11156 QCPLayerable *minimumDistanceLayerable = 0;
11157 for (int i = layerables.size() - 1; i >= 0; --i) {
11158 if (!layerables.at(i)->realVisibility()) continue;
11159 QVariant details;
11160 double dist = layerables.at(i)->selectTest(pos, onlySelectable, &details);
11161 if (dist >= 0 && dist < minimumDistance) {
11162 minimumDistance = dist;
11163 minimumDistanceLayerable = layerables.at(i);
11164 if (selectionDetails) *selectionDetails = details;
11165 }
11166 }
11167 if (minimumDistance < selectionTolerance()) return minimumDistanceLayerable;
11168 }
11169 return 0;
11170 }
11171
11172 /*!
11173 Saves the plot to a rastered image file \a fileName in the image format \a
11174 format. The plot is sized to \a width and \a height in pixels and scaled with
11175 \a scale. (width 100 and scale 2.0 lead to a full resolution file with width
11176 200.) If the \a format supports compression, \a quality may be between 0 and
11177 100 to control it.
11178
11179 Returns true on success. If this function fails, most likely the given \a
11180 format isn't supported by the system, see Qt docs about
11181 QImageWriter::supportedImageFormats().
11182
11183 \see saveBmp, saveJpg, savePng, savePdf
11184 */
11185 bool QCustomPlot::saveRastered(const QString &fileName, int width, int height,
11186 double scale, const char *format, int quality) {
11187 QPixmap buffer = toPixmap(width, height, scale);
11188 if (!buffer.isNull())
11189 return buffer.save(fileName, format, quality);
11190 else
11191 return false;
11192 }
11193
11194 /*!
11195 Renders the plot to a pixmap and returns it.
11196
11197 The plot is sized to \a width and \a height in pixels and scaled with \a
11198 scale. (width 100 and scale 2.0 lead to a full resolution pixmap with width
11199 200.)
11200
11201 \see toPainter, saveRastered, saveBmp, savePng, saveJpg, savePdf
11202 */
11203 QPixmap QCustomPlot::toPixmap(int width, int height, double scale) {
11204 // this method is somewhat similar to toPainter. Change something here, and a
11205 // change in toPainter might be necessary, too.
11206 int newWidth, newHeight;
11207 if (width == 0 || height == 0) {
11208 newWidth = this->width();
11209 newHeight = this->height();
11210 } else {
11211 newWidth = width;
11212 newHeight = height;
11213 }
11214 int scaledWidth = qRound(scale * newWidth);
11215 int scaledHeight = qRound(scale * newHeight);
11216
11217 QPixmap result(scaledWidth, scaledHeight);
11218 result.fill(
11219 mBackgroundBrush.style() == Qt::SolidPattern
11220 ? mBackgroundBrush.color()
11221 : Qt::transparent); // if using non-solid pattern, make transparent
11222 // now and draw brush pattern later
11223 QCPPainter painter;
11224 painter.begin(&result);
11225 if (painter.isActive()) {
11226 QRect oldViewport = viewport();
11227 setViewport(QRect(0, 0, newWidth, newHeight));
11228 painter.setMode(QCPPainter::pmNoCaching);
11229 if (!qFuzzyCompare(scale, 1.0)) {
11230 if (scale >
11231 1.0) // for scale < 1 we always want cosmetic pens where possible,
11232 // because else lines might disappear for very small scales
11233 painter.setMode(QCPPainter::pmNonCosmetic);
11234 painter.scale(scale, scale);
11235 }
11236 if (mBackgroundBrush.style() != Qt::SolidPattern &&
11237 mBackgroundBrush.style() !=
11238 Qt::NoBrush) // solid fills were done a few lines above with
11239 // QPixmap::fill
11240 painter.fillRect(mViewport, mBackgroundBrush);
11241 draw(&painter);
11242 setViewport(oldViewport);
11243 painter.end();
11244 } else // might happen if pixmap has width or height zero
11245 {
11246 qDebug() << Q_FUNC_INFO << "Couldn't activate painter on pixmap";
11247 return QPixmap();
11248 }
11249 return result;
11250 }
11251
11252 /*!
11253 Renders the plot using the passed \a painter.
11254
11255 The plot is sized to \a width and \a height in pixels. If the \a painter's
11256 scale is not 1.0, the resulting plot will appear scaled accordingly.
11257
11258 \note If you are restricted to using a QPainter (instead of QCPPainter),
11259 create a temporary QPicture and open a QCPPainter on it. Then call \ref
11260 toPainter with this QCPPainter. After ending the paint operation on the
11261 picture, draw it with the QPainter. This will reproduce the painter actions
11262 the QCPPainter took, with a QPainter.
11263
11264 \see toPixmap
11265 */
11266 void QCustomPlot::toPainter(QCPPainter *painter, int width, int height) {
11267 // this method is somewhat similar to toPixmap. Change something here, and a
11268 // change in toPixmap might be necessary, too.
11269 int newWidth, newHeight;
11270 if (width == 0 || height == 0) {
11271 newWidth = this->width();
11272 newHeight = this->height();
11273 } else {
11274 newWidth = width;
11275 newHeight = height;
11276 }
11277
11278 if (painter->isActive()) {
11279 QRect oldViewport = viewport();
11280 setViewport(QRect(0, 0, newWidth, newHeight));
11281 painter->setMode(QCPPainter::pmNoCaching);
11282 if (mBackgroundBrush.style() !=
11283 Qt::NoBrush) // unlike in toPixmap, we can't do QPixmap::fill for
11284 // Qt::SolidPattern brush style, so we also draw solid
11285 // fills with fillRect here
11286 painter->fillRect(mViewport, mBackgroundBrush);
11287 draw(painter);
11288 setViewport(oldViewport);
11289 } else
11290 qDebug() << Q_FUNC_INFO << "Passed painter is not active";
11291 }
11292
11293 ////////////////////////////////////////////////////////////////////////////////////////////////////
11294 //////////////////// QCPColorGradient
11295 ////////////////////////////////////////////////////////////////////////////////////////////////////
11296
11297 /*! \class QCPColorGradient
11298 \brief Defines a color gradient for use with e.g. \ref QCPColorMap
11299
11300 This class describes a color gradient which can be used to encode data with
11301 color. For example, QCPColorMap and QCPColorScale have \ref
11302 QCPColorMap::setGradient "setGradient" methods which take an instance of this
11303 class. Colors are set with \ref setColorStopAt(double position, const QColor
11304 &color) with a \a position from 0 to 1. In between these defined color
11305 positions, the color will be interpolated linearly either in RGB or HSV space,
11306 see \ref setColorInterpolation.
11307
11308 Alternatively, load one of the preset color gradients shown in the image
11309 below, with \ref loadPreset, or by directly specifying the preset in the
11310 constructor.
11311
11312 \image html QCPColorGradient.png
11313
11314 The fact that the \ref QCPColorGradient(GradientPreset preset) constructor
11315 allows directly converting a \ref GradientPreset to a QCPColorGradient, you
11316 can also directly pass \ref GradientPreset to all the \a setGradient methods,
11317 e.g.: \snippet documentation/doc-code-snippets/mainwindow.cpp
11318 qcpcolorgradient-setgradient
11319
11320 The total number of levels used in the gradient can be set with \ref
11321 setLevelCount. Whether the color gradient shall be applied periodically
11322 (wrapping around) to data values that lie outside the data range specified on
11323 the plottable instance can be controlled with \ref setPeriodic.
11324 */
11325
11326 /*!
11327 Constructs a new QCPColorGradient initialized with the colors and color
11328 interpolation according to \a preset.
11329
11330 The color level count is initialized to 350.
11331 */
11332 QCPColorGradient::QCPColorGradient(GradientPreset preset)
11333 : mLevelCount(350),
11334 mColorInterpolation(ciRGB),
11335 mPeriodic(false),
11336 mColorBufferInvalidated(true) {
11337 mColorBuffer.fill(qRgb(0, 0, 0), mLevelCount);
11338 loadPreset(preset);
11339 }
11340
11341 /* undocumented operator */
11342 bool QCPColorGradient::operator==(const QCPColorGradient &other) const {
11343 return ((other.mLevelCount == this->mLevelCount) &&
11344 (other.mColorInterpolation == this->mColorInterpolation) &&
11345 (other.mPeriodic == this->mPeriodic) &&
11346 (other.mColorStops == this->mColorStops));
11347 }
11348
11349 /*!
11350 Sets the number of discretization levels of the color gradient to \a n. The
11351 default is 350 which is typically enough to create a smooth appearance.
11352
11353 \image html QCPColorGradient-levelcount.png
11354 */
11355 void QCPColorGradient::setLevelCount(int n) {
11356 if (n < 2) {
11357 qDebug() << Q_FUNC_INFO << "n must be greater or equal 2 but was" << n;
11358 n = 2;
11359 }
11360 if (n != mLevelCount) {
11361 mLevelCount = n;
11362 mColorBufferInvalidated = true;
11363 }
11364 }
11365
11366 /*!
11367 Sets at which positions from 0 to 1 which color shall occur. The positions are
11368 the keys, the colors are the values of the passed QMap \a colorStops. In
11369 between these color stops, the color is interpolated according to \ref
11370 setColorInterpolation.
11371
11372 A more convenient way to create a custom gradient may be to clear all color
11373 stops with \ref clearColorStops and then adding them one by one with \ref
11374 setColorStopAt.
11375
11376 \see clearColorStops
11377 */
11378 void QCPColorGradient::setColorStops(const QMap<double, QColor> &colorStops) {
11379 mColorStops = colorStops;
11380 mColorBufferInvalidated = true;
11381 }
11382
11383 /*!
11384 Sets the \a color the gradient will have at the specified \a position (from 0
11385 to 1). In between these color stops, the color is interpolated according to
11386 \ref setColorInterpolation.
11387
11388 \see setColorStops, clearColorStops
11389 */
11390 void QCPColorGradient::setColorStopAt(double position, const QColor &color) {
11391 mColorStops.insert(position, color);
11392 mColorBufferInvalidated = true;
11393 }
11394
11395 /*!
11396 Sets whether the colors in between the configured color stops (see \ref
11397 setColorStopAt) shall be interpolated linearly in RGB or in HSV color space.
11398
11399 For example, a sweep in RGB space from red to green will have a muddy brown
11400 intermediate color, whereas in HSV space the intermediate color is yellow.
11401 */
11402 void QCPColorGradient::setColorInterpolation(
11403 QCPColorGradient::ColorInterpolation interpolation) {
11404 if (interpolation != mColorInterpolation) {
11405 mColorInterpolation = interpolation;
11406 mColorBufferInvalidated = true;
11407 }
11408 }
11409
11410 /*!
11411 Sets whether data points that are outside the configured data range (e.g. \ref
11412 QCPColorMap::setDataRange) are colored by periodically repeating the color
11413 gradient or whether they all have the same color, corresponding to the
11414 respective gradient boundary color.
11415
11416 \image html QCPColorGradient-periodic.png
11417
11418 As shown in the image above, gradients that have the same start and end color
11419 are especially suitable for a periodic gradient mapping, since they produce
11420 smooth color transitions throughout the color map. A preset that has this
11421 property is \ref gpHues.
11422
11423 In practice, using periodic color gradients makes sense when the data
11424 corresponds to a periodic dimension, such as an angle or a phase. If this is
11425 not the case, the color encoding might become ambiguous, because multiple
11426 different data values are shown as the same color.
11427 */
11428 void QCPColorGradient::setPeriodic(bool enabled) { mPeriodic = enabled; }
11429
11430 /*!
11431 This method is used to quickly convert a \a data array to colors. The colors
11432 will be output in the array \a scanLine. Both \a data and \a scanLine must
11433 have the length \a n when passed to this function. The data range that shall
11434 be used for mapping the data value to the gradient is passed in \a range. \a
11435 logarithmic indicates whether the data values shall be mapped to colors
11436 logarithmically.
11437
11438 if \a data actually contains 2D-data linearized via <tt>[row*columnCount +
11439 column]</tt>, you can set \a dataIndexFactor to <tt>columnCount</tt> to
11440 convert a column instead of a row of the data array, in \a scanLine. \a
11441 scanLine will remain a regular (1D) array. This works because \a data is
11442 addressed <tt>data[i*dataIndexFactor]</tt>.
11443 */
11444 void QCPColorGradient::colorize(const double *data, const QCPRange &range,
11445 QRgb *scanLine, int n, int dataIndexFactor,
11446 bool logarithmic) {
11447 // If you change something here, make sure to also adapt ::color()
11448 if (!data) {
11449 qDebug() << Q_FUNC_INFO << "null pointer given as data";
11450 return;
11451 }
11452 if (!scanLine) {
11453 qDebug() << Q_FUNC_INFO << "null pointer given as scanLine";
11454 return;
11455 }
11456 if (mColorBufferInvalidated) updateColorBuffer();
11457
11458 if (!logarithmic) {
11459 const double posToIndexFactor = (mLevelCount - 1) / range.size();
11460 if (mPeriodic) {
11461 for (int i = 0; i < n; ++i) {
11462 int index = (int)((data[dataIndexFactor * i] - range.lower) *
11463 posToIndexFactor) %
11464 mLevelCount;
11465 if (index < 0) index += mLevelCount;
11466 scanLine[i] = mColorBuffer.at(index);
11467 }
11468 } else {
11469 for (int i = 0; i < n; ++i) {
11470 int index =
11471 (data[dataIndexFactor * i] - range.lower) * posToIndexFactor;
11472 if (index < 0)
11473 index = 0;
11474 else if (index >= mLevelCount)
11475 index = mLevelCount - 1;
11476 scanLine[i] = mColorBuffer.at(index);
11477 }
11478 }
11479 } else // logarithmic == true
11480 {
11481 if (mPeriodic) {
11482 for (int i = 0; i < n; ++i) {
11483 int index = (int)(qLn(data[dataIndexFactor * i] / range.lower) /
11484 qLn(range.upper / range.lower) * (mLevelCount - 1)) %
11485 mLevelCount;
11486 if (index < 0) index += mLevelCount;
11487 scanLine[i] = mColorBuffer.at(index);
11488 }
11489 } else {
11490 for (int i = 0; i < n; ++i) {
11491 int index = qLn(data[dataIndexFactor * i] / range.lower) /
11492 qLn(range.upper / range.lower) * (mLevelCount - 1);
11493 if (index < 0)
11494 index = 0;
11495 else if (index >= mLevelCount)
11496 index = mLevelCount - 1;
11497 scanLine[i] = mColorBuffer.at(index);
11498 }
11499 }
11500 }
11501 }
11502
11503 /*! \internal
11504
11505 This method is used to colorize a single data value given in \a position, to
11506 colors. The data range that shall be used for mapping the data value to the
11507 gradient is passed in \a range. \a logarithmic indicates whether the data
11508 value shall be mapped to a color logarithmically.
11509
11510 If an entire array of data values shall be converted, rather use \ref
11511 colorize, for better performance.
11512 */
11513 QRgb QCPColorGradient::color(double position, const QCPRange &range,
11514 bool logarithmic) {
11515 // If you change something here, make sure to also adapt ::colorize()
11516 if (mColorBufferInvalidated) updateColorBuffer();
11517 int index = 0;
11518 if (!logarithmic)
11519 index = (position - range.lower) * (mLevelCount - 1) / range.size();
11520 else
11521 index = qLn(position / range.lower) / qLn(range.upper / range.lower) *
11522 (mLevelCount - 1);
11523 if (mPeriodic) {
11524 index = index % mLevelCount;
11525 if (index < 0) index += mLevelCount;
11526 } else {
11527 if (index < 0)
11528 index = 0;
11529 else if (index >= mLevelCount)
11530 index = mLevelCount - 1;
11531 }
11532 return mColorBuffer.at(index);
11533 }
11534
11535 /*!
11536 Clears the current color stops and loads the specified \a preset. A preset
11537 consists of predefined color stops and the corresponding color interpolation
11538 method.
11539
11540 The available presets are:
11541 \image html QCPColorGradient.png
11542 */
11543 void QCPColorGradient::loadPreset(GradientPreset preset) {
11544 clearColorStops();
11545 switch (preset) {
11546 case gpGrayscale:
11547 setColorInterpolation(ciRGB);
11548 setColorStopAt(0, Qt::black);
11549 setColorStopAt(1, Qt::white);
11550 break;
11551 case gpHot:
11552 setColorInterpolation(ciRGB);
11553 setColorStopAt(0, QColor(50, 0, 0));
11554 setColorStopAt(0.2, QColor(180, 10, 0));
11555 setColorStopAt(0.4, QColor(245, 50, 0));
11556 setColorStopAt(0.6, QColor(255, 150, 10));
11557 setColorStopAt(0.8, QColor(255, 255, 50));
11558 setColorStopAt(1, QColor(255, 255, 255));
11559 break;
11560 case gpCold:
11561 setColorInterpolation(ciRGB);
11562 setColorStopAt(0, QColor(0, 0, 50));
11563 setColorStopAt(0.2, QColor(0, 10, 180));
11564 setColorStopAt(0.4, QColor(0, 50, 245));
11565 setColorStopAt(0.6, QColor(10, 150, 255));
11566 setColorStopAt(0.8, QColor(50, 255, 255));
11567 setColorStopAt(1, QColor(255, 255, 255));
11568 break;
11569 case gpNight:
11570 setColorInterpolation(ciHSV);
11571 setColorStopAt(0, QColor(10, 20, 30));
11572 setColorStopAt(1, QColor(250, 255, 250));
11573 break;
11574 case gpCandy:
11575 setColorInterpolation(ciHSV);
11576 setColorStopAt(0, QColor(0, 0, 255));
11577 setColorStopAt(1, QColor(255, 250, 250));
11578 break;
11579 case gpGeography:
11580 setColorInterpolation(ciRGB);
11581 setColorStopAt(0, QColor(70, 170, 210));
11582 setColorStopAt(0.20, QColor(90, 160, 180));
11583 setColorStopAt(0.25, QColor(45, 130, 175));
11584 setColorStopAt(0.30, QColor(100, 140, 125));
11585 setColorStopAt(0.5, QColor(100, 140, 100));
11586 setColorStopAt(0.6, QColor(130, 145, 120));
11587 setColorStopAt(0.7, QColor(140, 130, 120));
11588 setColorStopAt(0.9, QColor(180, 190, 190));
11589 setColorStopAt(1, QColor(210, 210, 230));
11590 break;
11591 case gpIon:
11592 setColorInterpolation(ciHSV);
11593 setColorStopAt(0, QColor(50, 10, 10));
11594 setColorStopAt(0.45, QColor(0, 0, 255));
11595 setColorStopAt(0.8, QColor(0, 255, 255));
11596 setColorStopAt(1, QColor(0, 255, 0));
11597 break;
11598 case gpThermal:
11599 setColorInterpolation(ciRGB);
11600 setColorStopAt(0, QColor(0, 0, 50));
11601 setColorStopAt(0.15, QColor(20, 0, 120));
11602 setColorStopAt(0.33, QColor(200, 30, 140));
11603 setColorStopAt(0.6, QColor(255, 100, 0));
11604 setColorStopAt(0.85, QColor(255, 255, 40));
11605 setColorStopAt(1, QColor(255, 255, 255));
11606 break;
11607 case gpPolar:
11608 setColorInterpolation(ciRGB);
11609 setColorStopAt(0, QColor(50, 255, 255));
11610 setColorStopAt(0.18, QColor(10, 70, 255));
11611 setColorStopAt(0.28, QColor(10, 10, 190));
11612 setColorStopAt(0.5, QColor(0, 0, 0));
11613 setColorStopAt(0.72, QColor(190, 10, 10));
11614 setColorStopAt(0.82, QColor(255, 70, 10));
11615 setColorStopAt(1, QColor(255, 255, 50));
11616 break;
11617 case gpSpectrum:
11618 setColorInterpolation(ciHSV);
11619 setColorStopAt(0, QColor(50, 0, 50));
11620 setColorStopAt(0.15, QColor(0, 0, 255));
11621 setColorStopAt(0.35, QColor(0, 255, 255));
11622 setColorStopAt(0.6, QColor(255, 255, 0));
11623 setColorStopAt(0.75, QColor(255, 30, 0));
11624 setColorStopAt(1, QColor(50, 0, 0));
11625 break;
11626 case gpJet:
11627 setColorInterpolation(ciRGB);
11628 setColorStopAt(0, QColor(0, 0, 100));
11629 setColorStopAt(0.15, QColor(0, 50, 255));
11630 setColorStopAt(0.35, QColor(0, 255, 255));
11631 setColorStopAt(0.65, QColor(255, 255, 0));
11632 setColorStopAt(0.85, QColor(255, 30, 0));
11633 setColorStopAt(1, QColor(100, 0, 0));
11634 break;
11635 case gpHues:
11636 setColorInterpolation(ciHSV);
11637 setColorStopAt(0, QColor(255, 0, 0));
11638 setColorStopAt(1.0 / 3.0, QColor(0, 0, 255));
11639 setColorStopAt(2.0 / 3.0, QColor(0, 255, 0));
11640 setColorStopAt(1, QColor(255, 0, 0));
11641 break;
11642 }
11643 }
11644
11645 /*!
11646 Clears all color stops.
11647
11648 \see setColorStops, setColorStopAt
11649 */
11650 void QCPColorGradient::clearColorStops() {
11651 mColorStops.clear();
11652 mColorBufferInvalidated = true;
11653 }
11654
11655 /*!
11656 Returns an inverted gradient. The inverted gradient has all properties as this
11657 \ref QCPColorGradient, but the order of the color stops is inverted.
11658
11659 \see setColorStops, setColorStopAt
11660 */
11661 QCPColorGradient QCPColorGradient::inverted() const {
11662 QCPColorGradient result(*this);
11663 result.clearColorStops();
11664 for (QMap<double, QColor>::const_iterator it = mColorStops.constBegin();
11665 it != mColorStops.constEnd(); ++it)
11666 result.setColorStopAt(1.0 - it.key(), it.value());
11667 return result;
11668 }
11669
11670 /*! \internal
11671
11672 Updates the internal color buffer which will be used by \ref colorize and \ref
11673 color, to quickly convert positions to colors. This is where the interpolation
11674 between color stops is calculated.
11675 */
11676 void QCPColorGradient::updateColorBuffer() {
11677 if (mColorBuffer.size() != mLevelCount) mColorBuffer.resize(mLevelCount);
11678 if (mColorStops.size() > 1) {
11679 double indexToPosFactor = 1.0 / (double)(mLevelCount - 1);
11680 for (int i = 0; i < mLevelCount; ++i) {
11681 double position = i * indexToPosFactor;
11682 QMap<double, QColor>::const_iterator it =
11683 mColorStops.lowerBound(position);
11684 if (it == mColorStops.constEnd()) // position is on or after last stop,
11685 // use color of last stop
11686 {
11687 mColorBuffer[i] = (it - 1).value().rgb();
11688 } else if (it ==
11689 mColorStops.constBegin()) // position is on or before first
11690 // stop, use color of first stop
11691 {
11692 mColorBuffer[i] = it.value().rgb();
11693 } else // position is in between stops (or on an intermediate stop),
11694 // interpolate color
11695 {
11696 QMap<double, QColor>::const_iterator high = it;
11697 QMap<double, QColor>::const_iterator low = it - 1;
11698 double t = (position - low.key()) /
11699 (high.key() - low.key()); // interpolation factor 0..1
11700 switch (mColorInterpolation) {
11701 case ciRGB: {
11702 mColorBuffer[i] =
11703 qRgb((1 - t) * low.value().red() + t * high.value().red(),
11704 (1 - t) * low.value().green() + t * high.value().green(),
11705 (1 - t) * low.value().blue() + t * high.value().blue());
11706 break;
11707 }
11708 case ciHSV: {
11709 QColor lowHsv = low.value().toHsv();
11710 QColor highHsv = high.value().toHsv();
11711 double hue = 0;
11712 double hueDiff = highHsv.hueF() - lowHsv.hueF();
11713 if (hueDiff > 0.5)
11714 hue = lowHsv.hueF() - t * (1.0 - hueDiff);
11715 else if (hueDiff < -0.5)
11716 hue = lowHsv.hueF() + t * (1.0 + hueDiff);
11717 else
11718 hue = lowHsv.hueF() + t * hueDiff;
11719 if (hue < 0)
11720 hue += 1.0;
11721 else if (hue >= 1.0)
11722 hue -= 1.0;
11723 mColorBuffer[i] =
11724 QColor::fromHsvF(
11725 hue,
11726 (1 - t) * lowHsv.saturationF() + t * highHsv.saturationF(),
11727 (1 - t) * lowHsv.valueF() + t * highHsv.valueF())
11728 .rgb();
11729 break;
11730 }
11731 }
11732 }
11733 }
11734 } else if (mColorStops.size() == 1) {
11735 mColorBuffer.fill(mColorStops.constBegin().value().rgb());
11736 } else // mColorStops is empty, fill color buffer with black
11737 {
11738 mColorBuffer.fill(qRgb(0, 0, 0));
11739 }
11740 mColorBufferInvalidated = false;
11741 }
11742
11743 ////////////////////////////////////////////////////////////////////////////////////////////////////
11744 //////////////////// QCPAxisRect
11745 ////////////////////////////////////////////////////////////////////////////////////////////////////
11746
11747 /*! \class QCPAxisRect
11748 \brief Holds multiple axes and arranges them in a rectangular shape.
11749
11750 This class represents an axis rect, a rectangular area that is bounded on all
11751 sides with an arbitrary number of axes.
11752
11753 Initially QCustomPlot has one axis rect, accessible via
11754 QCustomPlot::axisRect(). However, the layout system allows to have multiple
11755 axis rects, e.g. arranged in a grid layout (QCustomPlot::plotLayout).
11756
11757 By default, QCPAxisRect comes with four axes, at bottom, top, left and right.
11758 They can be accessed via \ref axis by providing the respective axis type (\ref
11759 QCPAxis::AxisType) and index. If you need all axes in the axis rect, use \ref
11760 axes. The top and right axes are set to be invisible initially
11761 (QCPAxis::setVisible). To add more axes to a side, use \ref addAxis or \ref
11762 addAxes. To remove an axis, use \ref removeAxis.
11763
11764 The axis rect layerable itself only draws a background pixmap or color, if
11765 specified (\ref setBackground). It is placed on the "background" layer
11766 initially (see \ref QCPLayer for an explanation of the QCustomPlot layer
11767 system). The axes that are held by the axis rect can be placed on other
11768 layers, independently of the axis rect.
11769
11770 Every axis rect has a child layout of type \ref QCPLayoutInset. It is
11771 accessible via \ref insetLayout and can be used to have other layout elements
11772 (or even other layouts with multiple elements) hovering inside the axis rect.
11773
11774 If an axis rect is clicked and dragged, it processes this by moving certain
11775 axis ranges. The behaviour can be controlled with \ref setRangeDrag and \ref
11776 setRangeDragAxes. If the mouse wheel is scrolled while the cursor is on the
11777 axis rect, certain axes are scaled. This is controllable via \ref
11778 setRangeZoom, \ref setRangeZoomAxes and \ref setRangeZoomFactor. These
11779 interactions are only enabled if \ref QCustomPlot::setInteractions contains
11780 \ref QCP::iRangeDrag and \ref QCP::iRangeZoom.
11781
11782 \image html AxisRectSpacingOverview.png
11783 <center>Overview of the spacings and paddings that define the geometry of an
11784 axis. The dashed line on the far left indicates the viewport/widget
11785 border.</center>
11786 */
11787
11788 /* start documentation of inline functions */
11789
11790 /*! \fn QCPLayoutInset *QCPAxisRect::insetLayout() const
11791
11792 Returns the inset layout of this axis rect. It can be used to place other
11793 layout elements (or even layouts with multiple other elements) inside/on top
11794 of an axis rect.
11795
11796 \see QCPLayoutInset
11797 */
11798
11799 /*! \fn int QCPAxisRect::left() const
11800
11801 Returns the pixel position of the left border of this axis rect. Margins are
11802 not taken into account here, so the returned value is with respect to the
11803 inner \ref rect.
11804 */
11805
11806 /*! \fn int QCPAxisRect::right() const
11807
11808 Returns the pixel position of the right border of this axis rect. Margins are
11809 not taken into account here, so the returned value is with respect to the
11810 inner \ref rect.
11811 */
11812
11813 /*! \fn int QCPAxisRect::top() const
11814
11815 Returns the pixel position of the top border of this axis rect. Margins are
11816 not taken into account here, so the returned value is with respect to the
11817 inner \ref rect.
11818 */
11819
11820 /*! \fn int QCPAxisRect::bottom() const
11821
11822 Returns the pixel position of the bottom border of this axis rect. Margins are
11823 not taken into account here, so the returned value is with respect to the
11824 inner \ref rect.
11825 */
11826
11827 /*! \fn int QCPAxisRect::width() const
11828
11829 Returns the pixel width of this axis rect. Margins are not taken into account
11830 here, so the returned value is with respect to the inner \ref rect.
11831 */
11832
11833 /*! \fn int QCPAxisRect::height() const
11834
11835 Returns the pixel height of this axis rect. Margins are not taken into account
11836 here, so the returned value is with respect to the inner \ref rect.
11837 */
11838
11839 /*! \fn QSize QCPAxisRect::size() const
11840
11841 Returns the pixel size of this axis rect. Margins are not taken into account
11842 here, so the returned value is with respect to the inner \ref rect.
11843 */
11844
11845 /*! \fn QPoint QCPAxisRect::topLeft() const
11846
11847 Returns the top left corner of this axis rect in pixels. Margins are not taken
11848 into account here, so the returned value is with respect to the inner \ref
11849 rect.
11850 */
11851
11852 /*! \fn QPoint QCPAxisRect::topRight() const
11853
11854 Returns the top right corner of this axis rect in pixels. Margins are not
11855 taken into account here, so the returned value is with respect to the inner
11856 \ref rect.
11857 */
11858
11859 /*! \fn QPoint QCPAxisRect::bottomLeft() const
11860
11861 Returns the bottom left corner of this axis rect in pixels. Margins are not
11862 taken into account here, so the returned value is with respect to the inner
11863 \ref rect.
11864 */
11865
11866 /*! \fn QPoint QCPAxisRect::bottomRight() const
11867
11868 Returns the bottom right corner of this axis rect in pixels. Margins are not
11869 taken into account here, so the returned value is with respect to the inner
11870 \ref rect.
11871 */
11872
11873 /*! \fn QPoint QCPAxisRect::center() const
11874
11875 Returns the center of this axis rect in pixels. Margins are not taken into
11876 account here, so the returned value is with respect to the inner \ref rect.
11877 */
11878
11879 /* end documentation of inline functions */
11880
11881 /*!
11882 Creates a QCPAxisRect instance and sets default values. An axis is added for
11883 each of the four sides, the top and right axes are set invisible initially.
11884 */
11885 QCPAxisRect::QCPAxisRect(QCustomPlot *parentPlot, bool setupDefaultAxes)
11886 : QCPLayoutElement(parentPlot),
11887 mBackgroundBrush(Qt::NoBrush),
11888 mBackgroundScaled(true),
11889 mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding),
11890 mInsetLayout(new QCPLayoutInset),
11891 mRangeDrag(Qt::Horizontal | Qt::Vertical),
11892 mRangeZoom(Qt::Horizontal | Qt::Vertical),
11893 mRangeZoomFactorHorz(0.85),
11894 mRangeZoomFactorVert(0.85),
11895 mDragging(false) {
11896 mInsetLayout->initializeParentPlot(mParentPlot);
11897 mInsetLayout->setParentLayerable(this);
11898 mInsetLayout->setParent(this);
11899
11900 setMinimumSize(50, 50);
11901 setMinimumMargins(QMargins(15, 15, 15, 15));
11902 mAxes.insert(QCPAxis::atLeft, QList<QCPAxis *>());
11903 mAxes.insert(QCPAxis::atRight, QList<QCPAxis *>());
11904 mAxes.insert(QCPAxis::atTop, QList<QCPAxis *>());
11905 mAxes.insert(QCPAxis::atBottom, QList<QCPAxis *>());
11906
11907 if (setupDefaultAxes) {
11908 QCPAxis *xAxis = addAxis(QCPAxis::atBottom);
11909 QCPAxis *yAxis = addAxis(QCPAxis::atLeft);
11910 QCPAxis *xAxis2 = addAxis(QCPAxis::atTop);
11911 QCPAxis *yAxis2 = addAxis(QCPAxis::atRight);
11912 setRangeDragAxes(xAxis, yAxis);
11913 setRangeZoomAxes(xAxis, yAxis);
11914 xAxis2->setVisible(false);
11915 yAxis2->setVisible(false);
11916 xAxis->grid()->setVisible(true);
11917 yAxis->grid()->setVisible(true);
11918 xAxis2->grid()->setVisible(false);
11919 yAxis2->grid()->setVisible(false);
11920 xAxis2->grid()->setZeroLinePen(Qt::NoPen);
11921 yAxis2->grid()->setZeroLinePen(Qt::NoPen);
11922 xAxis2->grid()->setVisible(false);
11923 yAxis2->grid()->setVisible(false);
11924 }
11925 }
11926
11927 QCPAxisRect::~QCPAxisRect() {
11928 delete mInsetLayout;
11929 mInsetLayout = 0;
11930
11931 QList<QCPAxis *> axesList = axes();
11932 for (int i = 0; i < axesList.size(); ++i) removeAxis(axesList.at(i));
11933 }
11934
11935 /*!
11936 Returns the number of axes on the axis rect side specified with \a type.
11937
11938 \see axis
11939 */
11940 int QCPAxisRect::axisCount(QCPAxis::AxisType type) const {
11941 return mAxes.value(type).size();
11942 }
11943
11944 /*!
11945 Returns the axis with the given \a index on the axis rect side specified with
11946 \a type.
11947
11948 \see axisCount, axes
11949 */
11950 QCPAxis *QCPAxisRect::axis(QCPAxis::AxisType type, int index) const {
11951 QList<QCPAxis *> ax(mAxes.value(type));
11952 if (index >= 0 && index < ax.size()) {
11953 return ax.at(index);
11954 } else {
11955 qDebug() << Q_FUNC_INFO << "Axis index out of bounds:" << index;
11956 return 0;
11957 }
11958 }
11959
11960 /*!
11961 Returns all axes on the axis rect sides specified with \a types.
11962
11963 \a types may be a single \ref QCPAxis::AxisType or an <tt>or</tt>-combination,
11964 to get the axes of multiple sides.
11965
11966 \see axis
11967 */
11968 QList<QCPAxis *> QCPAxisRect::axes(QCPAxis::AxisTypes types) const {
11969 QList<QCPAxis *> result;
11970 if (types.testFlag(QCPAxis::atLeft)) result << mAxes.value(QCPAxis::atLeft);
11971 if (types.testFlag(QCPAxis::atRight)) result << mAxes.value(QCPAxis::atRight);
11972 if (types.testFlag(QCPAxis::atTop)) result << mAxes.value(QCPAxis::atTop);
11973 if (types.testFlag(QCPAxis::atBottom))
11974 result << mAxes.value(QCPAxis::atBottom);
11975 return result;
11976 }
11977
11978 /*! \overload
11979
11980 Returns all axes of this axis rect.
11981 */
11982 QList<QCPAxis *> QCPAxisRect::axes() const {
11983 QList<QCPAxis *> result;
11984 QHashIterator<QCPAxis::AxisType, QList<QCPAxis *> > it(mAxes);
11985 while (it.hasNext()) {
11986 it.next();
11987 result << it.value();
11988 }
11989 return result;
11990 }
11991
11992 /*!
11993 Adds a new axis to the axis rect side specified with \a type, and returns it.
11994 If \a axis is 0, a new QCPAxis instance is created internally.
11995
11996 You may inject QCPAxis instances (or sublasses of QCPAxis) by setting \a axis
11997 to an axis that was previously created outside QCustomPlot. It is important to
11998 note that QCustomPlot takes ownership of the axis, so you may not delete it
11999 afterwards. Further, the \a axis must have been created with this axis rect as
12000 parent and with the same axis type as specified in \a type. If this is not the
12001 case, a debug output is generated, the axis is not added, and the method
12002 returns 0.
12003
12004 This method can not be used to move \a axis between axis rects. The same \a
12005 axis instance must not be added multiple times to the same or different axis
12006 rects.
12007
12008 If an axis rect side already contains one or more axes, the lower and upper
12009 endings of the new axis (\ref QCPAxis::setLowerEnding, \ref
12010 QCPAxis::setUpperEnding) are set to \ref QCPLineEnding::esHalfBar.
12011
12012 \see addAxes, setupFullAxesBox
12013 */
12014 QCPAxis *QCPAxisRect::addAxis(QCPAxis::AxisType type, QCPAxis *axis) {
12015 QCPAxis *newAxis = axis;
12016 if (!newAxis) {
12017 newAxis = new QCPAxis(this, type);
12018 } else // user provided existing axis instance, do some sanity checks
12019 {
12020 if (newAxis->axisType() != type) {
12021 qDebug() << Q_FUNC_INFO
12022 << "passed axis has different axis type than specified in type "
12023 "parameter";
12024 return 0;
12025 }
12026 if (newAxis->axisRect() != this) {
12027 qDebug() << Q_FUNC_INFO
12028 << "passed axis doesn't have this axis rect as parent axis rect";
12029 return 0;
12030 }
12031 if (axes().contains(newAxis)) {
12032 qDebug() << Q_FUNC_INFO
12033 << "passed axis is already owned by this axis rect";
12034 return 0;
12035 }
12036 }
12037 if (mAxes[type].size() > 0) // multiple axes on one side, add half-bar axis
12038 // ending to additional axes with offset
12039 {
12040 bool invert = (type == QCPAxis::atRight) || (type == QCPAxis::atBottom);
12041 newAxis->setLowerEnding(
12042 QCPLineEnding(QCPLineEnding::esHalfBar, 6, 10, !invert));
12043 newAxis->setUpperEnding(
12044 QCPLineEnding(QCPLineEnding::esHalfBar, 6, 10, invert));
12045 }
12046 mAxes[type].append(newAxis);
12047 return newAxis;
12048 }
12049
12050 /*!
12051 Adds a new axis with \ref addAxis to each axis rect side specified in \a
12052 types. This may be an <tt>or</tt>-combination of QCPAxis::AxisType, so axes
12053 can be added to multiple sides at once.
12054
12055 Returns a list of the added axes.
12056
12057 \see addAxis, setupFullAxesBox
12058 */
12059 QList<QCPAxis *> QCPAxisRect::addAxes(QCPAxis::AxisTypes types) {
12060 QList<QCPAxis *> result;
12061 if (types.testFlag(QCPAxis::atLeft)) result << addAxis(QCPAxis::atLeft);
12062 if (types.testFlag(QCPAxis::atRight)) result << addAxis(QCPAxis::atRight);
12063 if (types.testFlag(QCPAxis::atTop)) result << addAxis(QCPAxis::atTop);
12064 if (types.testFlag(QCPAxis::atBottom)) result << addAxis(QCPAxis::atBottom);
12065 return result;
12066 }
12067
12068 /*!
12069 Removes the specified \a axis from the axis rect and deletes it.
12070
12071 Returns true on success, i.e. if \a axis was a valid axis in this axis rect.
12072
12073 \see addAxis
12074 */
12075 bool QCPAxisRect::removeAxis(QCPAxis *axis) {
12076 // don't access axis->axisType() to provide safety when axis is an invalid
12077 // pointer, rather go through all axis containers:
12078 QHashIterator<QCPAxis::AxisType, QList<QCPAxis *> > it(mAxes);
12079 while (it.hasNext()) {
12080 it.next();
12081 if (it.value().contains(axis)) {
12082 mAxes[it.key()].removeOne(axis);
12083 if (qobject_cast<QCustomPlot *>(
12084 parentPlot())) // make sure this isn't called from QObject dtor
12085 // when QCustomPlot is already destructed (happens
12086 // when the axis rect is not in any layout and
12087 // thus QObject-child of QCustomPlot)
12088 parentPlot()->axisRemoved(axis);
12089 delete axis;
12090 return true;
12091 }
12092 }
12093 qDebug() << Q_FUNC_INFO
12094 << "Axis isn't in axis rect:" << reinterpret_cast<quintptr>(axis);
12095 return false;
12096 }
12097
12098 /*!
12099 Convenience function to create an axis on each side that doesn't have any axes
12100 yet and set their visibility to true. Further, the top/right axes are assigned
12101 the following properties of the bottom/left axes:
12102
12103 \li range (\ref QCPAxis::setRange)
12104 \li range reversed (\ref QCPAxis::setRangeReversed)
12105 \li scale type (\ref QCPAxis::setScaleType)
12106 \li scale log base (\ref QCPAxis::setScaleLogBase)
12107 \li ticks (\ref QCPAxis::setTicks)
12108 \li auto (major) tick count (\ref QCPAxis::setAutoTickCount)
12109 \li sub tick count (\ref QCPAxis::setSubTickCount)
12110 \li auto sub ticks (\ref QCPAxis::setAutoSubTicks)
12111 \li tick step (\ref QCPAxis::setTickStep)
12112 \li auto tick step (\ref QCPAxis::setAutoTickStep)
12113 \li number format (\ref QCPAxis::setNumberFormat)
12114 \li number precision (\ref QCPAxis::setNumberPrecision)
12115 \li tick label type (\ref QCPAxis::setTickLabelType)
12116 \li date time format (\ref QCPAxis::setDateTimeFormat)
12117 \li date time spec (\ref QCPAxis::setDateTimeSpec)
12118
12119 Tick labels (\ref QCPAxis::setTickLabels) of the right and top axes are set to
12120 false.
12121
12122 If \a connectRanges is true, the \ref QCPAxis::rangeChanged "rangeChanged"
12123 signals of the bottom and left axes are connected to the \ref
12124 QCPAxis::setRange slots of the top and right axes.
12125 */
12126 void QCPAxisRect::setupFullAxesBox(bool connectRanges) {
12127 QCPAxis *xAxis, *yAxis, *xAxis2, *yAxis2;
12128 if (axisCount(QCPAxis::atBottom) == 0)
12129 xAxis = addAxis(QCPAxis::atBottom);
12130 else
12131 xAxis = axis(QCPAxis::atBottom);
12132
12133 if (axisCount(QCPAxis::atLeft) == 0)
12134 yAxis = addAxis(QCPAxis::atLeft);
12135 else
12136 yAxis = axis(QCPAxis::atLeft);
12137
12138 if (axisCount(QCPAxis::atTop) == 0)
12139 xAxis2 = addAxis(QCPAxis::atTop);
12140 else
12141 xAxis2 = axis(QCPAxis::atTop);
12142
12143 if (axisCount(QCPAxis::atRight) == 0)
12144 yAxis2 = addAxis(QCPAxis::atRight);
12145 else
12146 yAxis2 = axis(QCPAxis::atRight);
12147
12148 xAxis->setVisible(true);
12149 yAxis->setVisible(true);
12150 xAxis2->setVisible(true);
12151 yAxis2->setVisible(true);
12152 xAxis2->setTickLabels(false);
12153 yAxis2->setTickLabels(false);
12154
12155 xAxis2->setRange(xAxis->range());
12156 xAxis2->setRangeReversed(xAxis->rangeReversed());
12157 xAxis2->setScaleType(xAxis->scaleType());
12158 xAxis2->setScaleLogBase(xAxis->scaleLogBase());
12159 xAxis2->setTicks(xAxis->ticks());
12160 xAxis2->setAutoTickCount(xAxis->autoTickCount());
12161 xAxis2->setSubTickCount(xAxis->subTickCount());
12162 xAxis2->setAutoSubTicks(xAxis->autoSubTicks());
12163 xAxis2->setTickStep(xAxis->tickStep());
12164 xAxis2->setAutoTickStep(xAxis->autoTickStep());
12165 xAxis2->setNumberFormat(xAxis->numberFormat());
12166 xAxis2->setNumberPrecision(xAxis->numberPrecision());
12167 xAxis2->setTickLabelType(xAxis->tickLabelType());
12168 xAxis2->setDateTimeFormat(xAxis->dateTimeFormat());
12169 xAxis2->setDateTimeSpec(xAxis->dateTimeSpec());
12170
12171 yAxis2->setRange(yAxis->range());
12172 yAxis2->setRangeReversed(yAxis->rangeReversed());
12173 yAxis2->setScaleType(yAxis->scaleType());
12174 yAxis2->setScaleLogBase(yAxis->scaleLogBase());
12175 yAxis2->setTicks(yAxis->ticks());
12176 yAxis2->setAutoTickCount(yAxis->autoTickCount());
12177 yAxis2->setSubTickCount(yAxis->subTickCount());
12178 yAxis2->setAutoSubTicks(yAxis->autoSubTicks());
12179 yAxis2->setTickStep(yAxis->tickStep());
12180 yAxis2->setAutoTickStep(yAxis->autoTickStep());
12181 yAxis2->setNumberFormat(yAxis->numberFormat());
12182 yAxis2->setNumberPrecision(yAxis->numberPrecision());
12183 yAxis2->setTickLabelType(yAxis->tickLabelType());
12184 yAxis2->setDateTimeFormat(yAxis->dateTimeFormat());
12185 yAxis2->setDateTimeSpec(yAxis->dateTimeSpec());
12186
12187 if (connectRanges) {
12188 connect(xAxis, SIGNAL(rangeChanged(QCPRange)), xAxis2,
12189 SLOT(setRange(QCPRange)));
12190 connect(yAxis, SIGNAL(rangeChanged(QCPRange)), yAxis2,
12191 SLOT(setRange(QCPRange)));
12192 }
12193 }
12194
12195 /*!
12196 Returns a list of all the plottables that are associated with this axis rect.
12197
12198 A plottable is considered associated with an axis rect if its key or value
12199 axis (or both) is in this axis rect.
12200
12201 \see graphs, items
12202 */
12203 QList<QCPAbstractPlottable *> QCPAxisRect::plottables() const {
12204 // Note: don't append all QCPAxis::plottables() into a list, because we might
12205 // get duplicate entries
12206 QList<QCPAbstractPlottable *> result;
12207 for (int i = 0; i < mParentPlot->mPlottables.size(); ++i) {
12208 if (mParentPlot->mPlottables.at(i)->keyAxis()->axisRect() == this ||
12209 mParentPlot->mPlottables.at(i)->valueAxis()->axisRect() == this)
12210 result.append(mParentPlot->mPlottables.at(i));
12211 }
12212 return result;
12213 }
12214
12215 /*!
12216 Returns a list of all the graphs that are associated with this axis rect.
12217
12218 A graph is considered associated with an axis rect if its key or value axis
12219 (or both) is in this axis rect.
12220
12221 \see plottables, items
12222 */
12223 QList<QCPGraph *> QCPAxisRect::graphs() const {
12224 // Note: don't append all QCPAxis::graphs() into a list, because we might get
12225 // duplicate entries
12226 QList<QCPGraph *> result;
12227 for (int i = 0; i < mParentPlot->mGraphs.size(); ++i) {
12228 if (mParentPlot->mGraphs.at(i)->keyAxis()->axisRect() == this ||
12229 mParentPlot->mGraphs.at(i)->valueAxis()->axisRect() == this)
12230 result.append(mParentPlot->mGraphs.at(i));
12231 }
12232 return result;
12233 }
12234
12235 /*!
12236 Returns a list of all the items that are associated with this axis rect.
12237
12238 An item is considered associated with an axis rect if any of its positions has
12239 key or value axis set to an axis that is in this axis rect, or if any of its
12240 positions has \ref QCPItemPosition::setAxisRect set to the axis rect, or if
12241 the clip axis rect (\ref QCPAbstractItem::setClipAxisRect) is set to this axis
12242 rect.
12243
12244 \see plottables, graphs
12245 */
12246 QList<QCPAbstractItem *> QCPAxisRect::items() const {
12247 // Note: don't just append all QCPAxis::items() into a list, because we might
12248 // get duplicate entries
12249 // and miss those items that have this axis rect as clipAxisRect.
12250 QList<QCPAbstractItem *> result;
12251 for (int itemId = 0; itemId < mParentPlot->mItems.size(); ++itemId) {
12252 if (mParentPlot->mItems.at(itemId)->clipAxisRect() == this) {
12253 result.append(mParentPlot->mItems.at(itemId));
12254 continue;
12255 }
12256 QList<QCPItemPosition *> positions =
12257 mParentPlot->mItems.at(itemId)->positions();
12258 for (int posId = 0; posId < positions.size(); ++posId) {
12259 if (positions.at(posId)->axisRect() == this ||
12260 positions.at(posId)->keyAxis()->axisRect() == this ||
12261 positions.at(posId)->valueAxis()->axisRect() == this) {
12262 result.append(mParentPlot->mItems.at(itemId));
12263 break;
12264 }
12265 }
12266 }
12267 return result;
12268 }
12269
12270 /*!
12271 This method is called automatically upon replot and doesn't need to be called
12272 by users of QCPAxisRect.
12273
12274 Calls the base class implementation to update the margins (see \ref
12275 QCPLayoutElement::update), and finally passes the \ref rect to the inset
12276 layout (\ref insetLayout) and calls its QCPInsetLayout::update function.
12277 */
12278 void QCPAxisRect::update(UpdatePhase phase) {
12279 QCPLayoutElement::update(phase);
12280
12281 switch (phase) {
12282 case upPreparation: {
12283 QList<QCPAxis *> allAxes = axes();
12284 for (int i = 0; i < allAxes.size(); ++i)
12285 allAxes.at(i)->setupTickVectors();
12286 break;
12287 }
12288 case upLayout: {
12289 mInsetLayout->setOuterRect(rect());
12290 break;
12291 }
12292 default:
12293 break;
12294 }
12295
12296 // pass update call on to inset layout (doesn't happen automatically, because
12297 // QCPAxisRect doesn't derive from QCPLayout):
12298 mInsetLayout->update(phase);
12299 }
12300
12301 /* inherits documentation from base class */
12302 QList<QCPLayoutElement *> QCPAxisRect::elements(bool recursive) const {
12303 QList<QCPLayoutElement *> result;
12304 if (mInsetLayout) {
12305 result << mInsetLayout;
12306 if (recursive) result << mInsetLayout->elements(recursive);
12307 }
12308 return result;
12309 }
12310
12311 /* inherits documentation from base class */
12312 void QCPAxisRect::applyDefaultAntialiasingHint(QCPPainter *painter) const {
12313 painter->setAntialiasing(false);
12314 }
12315
12316 /* inherits documentation from base class */
12317 void QCPAxisRect::draw(QCPPainter *painter) { drawBackground(painter); }
12318
12319 /*!
12320 Sets \a pm as the axis background pixmap. The axis background pixmap will be
12321 drawn inside the axis rect. Since axis rects place themselves on the
12322 "background" layer by default, the axis rect backgrounds are usually drawn
12323 below everything else.
12324
12325 For cases where the provided pixmap doesn't have the same size as the axis
12326 rect, scaling can be enabled with \ref setBackgroundScaled and the scaling
12327 mode (i.e. whether and how the aspect ratio is preserved) can be set with \ref
12328 setBackgroundScaledMode. To set all these options in one call, consider using
12329 the overloaded version of this function.
12330
12331 Below the pixmap, the axis rect may be optionally filled with a brush, if
12332 specified with \ref setBackground(const QBrush &brush).
12333
12334 \see setBackgroundScaled, setBackgroundScaledMode, setBackground(const QBrush
12335 &brush)
12336 */
12337 void QCPAxisRect::setBackground(const QPixmap &pm) {
12338 mBackgroundPixmap = pm;
12339 mScaledBackgroundPixmap = QPixmap();
12340 }
12341
12342 /*! \overload
12343
12344 Sets \a brush as the background brush. The axis rect background will be filled
12345 with this brush. Since axis rects place themselves on the "background" layer
12346 by default, the axis rect backgrounds are usually drawn below everything else.
12347
12348 The brush will be drawn before (under) any background pixmap, which may be
12349 specified with \ref setBackground(const QPixmap &pm).
12350
12351 To disable drawing of a background brush, set \a brush to Qt::NoBrush.
12352
12353 \see setBackground(const QPixmap &pm)
12354 */
12355 void QCPAxisRect::setBackground(const QBrush &brush) {
12356 mBackgroundBrush = brush;
12357 }
12358
12359 /*! \overload
12360
12361 Allows setting the background pixmap of the axis rect, whether it shall be
12362 scaled and how it shall be scaled in one call.
12363
12364 \see setBackground(const QPixmap &pm), setBackgroundScaled,
12365 setBackgroundScaledMode
12366 */
12367 void QCPAxisRect::setBackground(const QPixmap &pm, bool scaled,
12368 Qt::AspectRatioMode mode) {
12369 mBackgroundPixmap = pm;
12370 mScaledBackgroundPixmap = QPixmap();
12371 mBackgroundScaled = scaled;
12372 mBackgroundScaledMode = mode;
12373 }
12374
12375 /*!
12376 Sets whether the axis background pixmap shall be scaled to fit the axis rect
12377 or not. If \a scaled is set to true, you may control whether and how the
12378 aspect ratio of the original pixmap is preserved with \ref
12379 setBackgroundScaledMode.
12380
12381 Note that the scaled version of the original pixmap is buffered, so there is
12382 no performance penalty on replots. (Except when the axis rect dimensions are
12383 changed continuously.)
12384
12385 \see setBackground, setBackgroundScaledMode
12386 */
12387 void QCPAxisRect::setBackgroundScaled(bool scaled) {
12388 mBackgroundScaled = scaled;
12389 }
12390
12391 /*!
12392 If scaling of the axis background pixmap is enabled (\ref
12393 setBackgroundScaled), use this function to define whether and how the aspect
12394 ratio of the original pixmap passed to \ref setBackground is preserved. \see
12395 setBackground, setBackgroundScaled
12396 */
12397 void QCPAxisRect::setBackgroundScaledMode(Qt::AspectRatioMode mode) {
12398 mBackgroundScaledMode = mode;
12399 }
12400
12401 /*!
12402 Returns the range drag axis of the \a orientation provided.
12403
12404 \see setRangeDragAxes
12405 */
12406 QCPAxis *QCPAxisRect::rangeDragAxis(Qt::Orientation orientation) {
12407 return (orientation == Qt::Horizontal ? mRangeDragHorzAxis.data()
12408 : mRangeDragVertAxis.data());
12409 }
12410
12411 /*!
12412 Returns the range zoom axis of the \a orientation provided.
12413
12414 \see setRangeZoomAxes
12415 */
12416 QCPAxis *QCPAxisRect::rangeZoomAxis(Qt::Orientation orientation) {
12417 return (orientation == Qt::Horizontal ? mRangeZoomHorzAxis.data()
12418 : mRangeZoomVertAxis.data());
12419 }
12420
12421 /*!
12422 Returns the range zoom factor of the \a orientation provided.
12423
12424 \see setRangeZoomFactor
12425 */
12426 double QCPAxisRect::rangeZoomFactor(Qt::Orientation orientation) {
12427 return (orientation == Qt::Horizontal ? mRangeZoomFactorHorz
12428 : mRangeZoomFactorVert);
12429 }
12430
12431 /*!
12432 Sets which axis orientation may be range dragged by the user with mouse
12433 interaction. What orientation corresponds to which specific axis can be set
12434 with \ref setRangeDragAxes(QCPAxis *horizontal, QCPAxis *vertical). By
12435 default, the horizontal axis is the bottom axis (xAxis) and the vertical axis
12436 is the left axis (yAxis).
12437
12438 To disable range dragging entirely, pass 0 as \a orientations or remove \ref
12439 QCP::iRangeDrag from \ref QCustomPlot::setInteractions. To enable range
12440 dragging for both directions, pass <tt>Qt::Horizontal | Qt::Vertical</tt> as
12441 \a orientations.
12442
12443 In addition to setting \a orientations to a non-zero value, make sure \ref
12444 QCustomPlot::setInteractions contains \ref QCP::iRangeDrag to enable the range
12445 dragging interaction.
12446
12447 \see setRangeZoom, setRangeDragAxes, QCustomPlot::setNoAntialiasingOnDrag
12448 */
12449 void QCPAxisRect::setRangeDrag(Qt::Orientations orientations) {
12450 mRangeDrag = orientations;
12451 }
12452
12453 /*!
12454 Sets which axis orientation may be zoomed by the user with the mouse wheel.
12455 What orientation corresponds to which specific axis can be set with \ref
12456 setRangeZoomAxes(QCPAxis *horizontal, QCPAxis *vertical). By default, the
12457 horizontal axis is the bottom axis (xAxis) and the vertical axis is the left
12458 axis (yAxis).
12459
12460 To disable range zooming entirely, pass 0 as \a orientations or remove \ref
12461 QCP::iRangeZoom from \ref QCustomPlot::setInteractions. To enable range
12462 zooming for both directions, pass <tt>Qt::Horizontal | Qt::Vertical</tt> as \a
12463 orientations.
12464
12465 In addition to setting \a orientations to a non-zero value, make sure \ref
12466 QCustomPlot::setInteractions contains \ref QCP::iRangeZoom to enable the range
12467 zooming interaction.
12468
12469 \see setRangeZoomFactor, setRangeZoomAxes, setRangeDrag
12470 */
12471 void QCPAxisRect::setRangeZoom(Qt::Orientations orientations) {
12472 mRangeZoom = orientations;
12473 }
12474
12475 /*!
12476 Sets the axes whose range will be dragged when \ref setRangeDrag enables mouse
12477 range dragging on the QCustomPlot widget.
12478
12479 \see setRangeZoomAxes
12480 */
12481 void QCPAxisRect::setRangeDragAxes(QCPAxis *horizontal, QCPAxis *vertical) {
12482 mRangeDragHorzAxis = horizontal;
12483 mRangeDragVertAxis = vertical;
12484 }
12485
12486 /*!
12487 Sets the axes whose range will be zoomed when \ref setRangeZoom enables mouse
12488 wheel zooming on the QCustomPlot widget. The two axes can be zoomed with
12489 different strengths, when different factors are passed to \ref
12490 setRangeZoomFactor(double horizontalFactor, double verticalFactor).
12491
12492 \see setRangeDragAxes
12493 */
12494 void QCPAxisRect::setRangeZoomAxes(QCPAxis *horizontal, QCPAxis *vertical) {
12495 mRangeZoomHorzAxis = horizontal;
12496 mRangeZoomVertAxis = vertical;
12497 }
12498
12499 /*!
12500 Sets how strong one rotation step of the mouse wheel zooms, when range zoom
12501 was activated with \ref setRangeZoom. The two parameters \a horizontalFactor
12502 and \a verticalFactor provide a way to let the horizontal axis zoom at
12503 different rates than the vertical axis. Which axis is horizontal and which is
12504 vertical, can be set with \ref setRangeZoomAxes.
12505
12506 When the zoom factor is greater than one, scrolling the mouse wheel backwards
12507 (towards the user) will zoom in (make the currently visible range smaller).
12508 For zoom factors smaller than one, the same scrolling direction will zoom out.
12509 */
12510 void QCPAxisRect::setRangeZoomFactor(double horizontalFactor,
12511 double verticalFactor) {
12512 mRangeZoomFactorHorz = horizontalFactor;
12513 mRangeZoomFactorVert = verticalFactor;
12514 }
12515
12516 /*! \overload
12517
12518 Sets both the horizontal and vertical zoom \a factor.
12519 */
12520 void QCPAxisRect::setRangeZoomFactor(double factor) {
12521 mRangeZoomFactorHorz = factor;
12522 mRangeZoomFactorVert = factor;
12523 }
12524
12525 /*! \internal
12526
12527 Draws the background of this axis rect. It may consist of a background fill (a
12528 QBrush) and a pixmap.
12529
12530 If a brush was given via \ref setBackground(const QBrush &brush), this
12531 function first draws an according filling inside the axis rect with the
12532 provided \a painter.
12533
12534 Then, if a pixmap was provided via \ref setBackground, this function buffers
12535 the scaled version depending on \ref setBackgroundScaled and \ref
12536 setBackgroundScaledMode and then draws it inside the axis rect with the
12537 provided \a painter. The scaled version is buffered in mScaledBackgroundPixmap
12538 to prevent expensive rescaling at every redraw. It is only updated, when the
12539 axis rect has changed in a way that requires a rescale of the background
12540 pixmap (this is dependant on the \ref setBackgroundScaledMode), or when a
12541 differend axis backgroud pixmap was set.
12542
12543 \see setBackground, setBackgroundScaled, setBackgroundScaledMode
12544 */
12545 void QCPAxisRect::drawBackground(QCPPainter *painter) {
12546 // draw background fill:
12547 if (mBackgroundBrush != Qt::NoBrush)
12548 painter->fillRect(mRect, mBackgroundBrush);
12549
12550 // draw background pixmap (on top of fill, if brush specified):
12551 if (!mBackgroundPixmap.isNull()) {
12552 if (mBackgroundScaled) {
12553 // check whether mScaledBackground needs to be updated:
12554 QSize scaledSize(mBackgroundPixmap.size());
12555 scaledSize.scale(mRect.size(), mBackgroundScaledMode);
12556 if (mScaledBackgroundPixmap.size() != scaledSize)
12557 mScaledBackgroundPixmap = mBackgroundPixmap.scaled(
12558 mRect.size(), mBackgroundScaledMode, Qt::SmoothTransformation);
12559 painter->drawPixmap(mRect.topLeft() + QPoint(0, -1),
12560 mScaledBackgroundPixmap,
12561 QRect(0, 0, mRect.width(), mRect.height()) &
12562 mScaledBackgroundPixmap.rect());
12563 } else {
12564 painter->drawPixmap(mRect.topLeft() + QPoint(0, -1), mBackgroundPixmap,
12565 QRect(0, 0, mRect.width(), mRect.height()));
12566 }
12567 }
12568 }
12569
12570 /*! \internal
12571
12572 This function makes sure multiple axes on the side specified with \a type
12573 don't collide, but are distributed according to their respective space
12574 requirement (QCPAxis::calculateMargin).
12575
12576 It does this by setting an appropriate offset (\ref QCPAxis::setOffset) on all
12577 axes except the one with index zero.
12578
12579 This function is called by \ref calculateAutoMargin.
12580 */
12581 void QCPAxisRect::updateAxesOffset(QCPAxis::AxisType type) {
12582 const QList<QCPAxis *> axesList = mAxes.value(type);
12583 if (axesList.isEmpty()) return;
12584
12585 bool isFirstVisible =
12586 !axesList.first()
12587 ->visible(); // if the first axis is visible, the second axis (which
12588 // is where the loop starts) isn't the first visible
12589 // axis, so initialize with false
12590 for (int i = 1; i < axesList.size(); ++i) {
12591 int offset =
12592 axesList.at(i - 1)->offset() + axesList.at(i - 1)->calculateMargin();
12593 if (axesList.at(i)
12594 ->visible()) // only add inner tick length to offset if this axis
12595 // is visible and it's not the first visible one
12596 // (might happen if true first axis is invisible)
12597 {
12598 if (!isFirstVisible) offset += axesList.at(i)->tickLengthIn();
12599 isFirstVisible = false;
12600 }
12601 axesList.at(i)->setOffset(offset);
12602 }
12603 }
12604
12605 /* inherits documentation from base class */
12606 int QCPAxisRect::calculateAutoMargin(QCP::MarginSide side) {
12607 if (!mAutoMargins.testFlag(side))
12608 qDebug() << Q_FUNC_INFO
12609 << "Called with side that isn't specified as auto margin";
12610
12611 updateAxesOffset(QCPAxis::marginSideToAxisType(side));
12612
12613 // note: only need to look at the last (outer most) axis to determine the
12614 // total margin, due to updateAxisOffset call
12615 const QList<QCPAxis *> axesList =
12616 mAxes.value(QCPAxis::marginSideToAxisType(side));
12617 if (axesList.size() > 0)
12618 return axesList.last()->offset() + axesList.last()->calculateMargin();
12619 else
12620 return 0;
12621 }
12622
12623 /*! \internal
12624
12625 Event handler for when a mouse button is pressed on the axis rect. If the left
12626 mouse button is pressed, the range dragging interaction is initialized (the
12627 actual range manipulation happens in the \ref mouseMoveEvent).
12628
12629 The mDragging flag is set to true and some anchor points are set that are
12630 needed to determine the distance the mouse was dragged in the mouse
12631 move/release events later.
12632
12633 \see mouseMoveEvent, mouseReleaseEvent
12634 */
12635 void QCPAxisRect::mousePressEvent(QMouseEvent *event) {
12636 mDragStart =
12637 event->pos(); // need this even when not LeftButton is pressed, to
12638 // determine in releaseEvent whether it was a full click
12639 // (no position change between press and release)
12640 if (event->buttons() & Qt::LeftButton) {
12641 mDragging = true;
12642 // initialize antialiasing backup in case we start dragging:
12643 if (mParentPlot->noAntialiasingOnDrag()) {
12644 mAADragBackup = mParentPlot->antialiasedElements();
12645 mNotAADragBackup = mParentPlot->notAntialiasedElements();
12646 }
12647 // Mouse range dragging interaction:
12648 if (mParentPlot->interactions().testFlag(QCP::iRangeDrag)) {
12649 if (mRangeDragHorzAxis)
12650 mDragStartHorzRange = mRangeDragHorzAxis.data()->range();
12651 if (mRangeDragVertAxis)
12652 mDragStartVertRange = mRangeDragVertAxis.data()->range();
12653 }
12654 }
12655 }
12656
12657 /*! \internal
12658
12659 Event handler for when the mouse is moved on the axis rect. If range dragging
12660 was activated in a preceding \ref mousePressEvent, the range is moved
12661 accordingly.
12662
12663 \see mousePressEvent, mouseReleaseEvent
12664 */
12665 void QCPAxisRect::mouseMoveEvent(QMouseEvent *event) {
12666 // Mouse range dragging interaction:
12667 if (mDragging && mParentPlot->interactions().testFlag(QCP::iRangeDrag)) {
12668 if (mRangeDrag.testFlag(Qt::Horizontal)) {
12669 if (QCPAxis *rangeDragHorzAxis = mRangeDragHorzAxis.data()) {
12670 if (rangeDragHorzAxis->mScaleType == QCPAxis::stLinear) {
12671 double diff = rangeDragHorzAxis->pixelToCoord(mDragStart.x()) -
12672 rangeDragHorzAxis->pixelToCoord(event->pos().x());
12673 rangeDragHorzAxis->setRange(mDragStartHorzRange.lower + diff,
12674 mDragStartHorzRange.upper + diff);
12675 } else if (rangeDragHorzAxis->mScaleType == QCPAxis::stLogarithmic) {
12676 double diff = rangeDragHorzAxis->pixelToCoord(mDragStart.x()) /
12677 rangeDragHorzAxis->pixelToCoord(event->pos().x());
12678 rangeDragHorzAxis->setRange(mDragStartHorzRange.lower * diff,
12679 mDragStartHorzRange.upper * diff);
12680 }
12681 }
12682 }
12683 if (mRangeDrag.testFlag(Qt::Vertical)) {
12684 if (QCPAxis *rangeDragVertAxis = mRangeDragVertAxis.data()) {
12685 if (rangeDragVertAxis->mScaleType == QCPAxis::stLinear) {
12686 double diff = rangeDragVertAxis->pixelToCoord(mDragStart.y()) -
12687 rangeDragVertAxis->pixelToCoord(event->pos().y());
12688 rangeDragVertAxis->setRange(mDragStartVertRange.lower + diff,
12689 mDragStartVertRange.upper + diff);
12690 } else if (rangeDragVertAxis->mScaleType == QCPAxis::stLogarithmic) {
12691 double diff = rangeDragVertAxis->pixelToCoord(mDragStart.y()) /
12692 rangeDragVertAxis->pixelToCoord(event->pos().y());
12693 rangeDragVertAxis->setRange(mDragStartVertRange.lower * diff,
12694 mDragStartVertRange.upper * diff);
12695 }
12696 }
12697 }
12698 if (mRangeDrag !=
12699 0) // if either vertical or horizontal drag was enabled, do a replot
12700 {
12701 if (mParentPlot->noAntialiasingOnDrag())
12702 mParentPlot->setNotAntialiasedElements(QCP::aeAll);
12703 mParentPlot->replot();
12704 }
12705 }
12706 }
12707
12708 /* inherits documentation from base class */
12709 void QCPAxisRect::mouseReleaseEvent(QMouseEvent *event) {
12710 Q_UNUSED(event)
12711 mDragging = false;
12712 if (mParentPlot->noAntialiasingOnDrag()) {
12713 mParentPlot->setAntialiasedElements(mAADragBackup);
12714 mParentPlot->setNotAntialiasedElements(mNotAADragBackup);
12715 }
12716 }
12717
12718 /*! \internal
12719
12720 Event handler for mouse wheel events. If rangeZoom is Qt::Horizontal,
12721 Qt::Vertical or both, the ranges of the axes defined as rangeZoomHorzAxis and
12722 rangeZoomVertAxis are scaled. The center of the scaling operation is the
12723 current cursor position inside the axis rect. The scaling factor is dependant
12724 on the mouse wheel delta (which direction the wheel was rotated) to provide a
12725 natural zooming feel. The Strength of the zoom can be controlled via \ref
12726 setRangeZoomFactor.
12727
12728 Note, that event->delta() is usually +/-120 for single rotation steps.
12729 However, if the mouse wheel is turned rapidly, many steps may bunch up to one
12730 event, so the event->delta() may then be multiples of 120. This is taken into
12731 account here, by calculating \a wheelSteps and using it as exponent of the
12732 range zoom factor. This takes care of the wheel direction automatically, by
12733 inverting the factor, when the wheel step is negative (f^-1 = 1/f).
12734 */
12735 void QCPAxisRect::wheelEvent(QWheelEvent *event) {
12736 // Mouse range zooming interaction:
12737 if (mParentPlot->interactions().testFlag(QCP::iRangeZoom)) {
12738 if (mRangeZoom != 0) {
12739 double factor;
12740 double wheelSteps =
12741 event->delta() / 120.0; // a single step delta is +/-120 usually
12742 if (mRangeZoom.testFlag(Qt::Horizontal)) {
12743 factor = qPow(mRangeZoomFactorHorz, wheelSteps);
12744 if (mRangeZoomHorzAxis.data())
12745 mRangeZoomHorzAxis.data()->scaleRange(
12746 factor,
12747 mRangeZoomHorzAxis.data()->pixelToCoord(event->pos().x()));
12748 }
12749 if (mRangeZoom.testFlag(Qt::Vertical)) {
12750 factor = qPow(mRangeZoomFactorVert, wheelSteps);
12751 if (mRangeZoomVertAxis.data())
12752 mRangeZoomVertAxis.data()->scaleRange(
12753 factor,
12754 mRangeZoomVertAxis.data()->pixelToCoord(event->pos().y()));
12755 }
12756 mParentPlot->replot();
12757 }
12758 }
12759 }
12760
12761 ////////////////////////////////////////////////////////////////////////////////////////////////////
12762 //////////////////// QCPAbstractLegendItem
12763 ////////////////////////////////////////////////////////////////////////////////////////////////////
12764
12765 /*! \class QCPAbstractLegendItem
12766 \brief The abstract base class for all entries in a QCPLegend.
12767
12768 It defines a very basic interface for entries in a QCPLegend. For representing
12769 plottables in the legend, the subclass \ref QCPPlottableLegendItem is more
12770 suitable.
12771
12772 Only derive directly from this class when you need absolute freedom (e.g. a
12773 custom legend entry that's not even associated with a plottable).
12774
12775 You must implement the following pure virtual functions:
12776 \li \ref draw (from QCPLayerable)
12777
12778 You inherit the following members you may use:
12779 <table>
12780 <tr>
12781 <td>QCPLegend *\b mParentLegend</td>
12782 <td>A pointer to the parent QCPLegend.</td>
12783 </tr><tr>
12784 <td>QFont \b mFont</td>
12785 <td>The generic font of the item. You should use this font for all or at
12786 least the most prominent text of the item.</td>
12787 </tr>
12788 </table>
12789 */
12790
12791 /* start of documentation of signals */
12792
12793 /*! \fn void QCPAbstractLegendItem::selectionChanged(bool selected)
12794
12795 This signal is emitted when the selection state of this legend item has
12796 changed, either by user interaction or by a direct call to \ref setSelected.
12797 */
12798
12799 /* end of documentation of signals */
12800
12801 /*!
12802 Constructs a QCPAbstractLegendItem and associates it with the QCPLegend \a
12803 parent. This does not cause the item to be added to \a parent, so \ref
12804 QCPLegend::addItem must be called separately.
12805 */
12806 QCPAbstractLegendItem::QCPAbstractLegendItem(QCPLegend *parent)
12807 : QCPLayoutElement(parent->parentPlot()),
12808 mParentLegend(parent),
12809 mFont(parent->font()),
12810 mTextColor(parent->textColor()),
12811 mSelectedFont(parent->selectedFont()),
12812 mSelectedTextColor(parent->selectedTextColor()),
12813 mSelectable(true),
12814 mSelected(false) {
12815 setLayer(QLatin1String("legend"));
12816 setMargins(QMargins(8, 2, 8, 2));
12817 }
12818
12819 /*!
12820 Sets the default font of this specific legend item to \a font.
12821
12822 \see setTextColor, QCPLegend::setFont
12823 */
12824 void QCPAbstractLegendItem::setFont(const QFont &font) { mFont = font; }
12825
12826 /*!
12827 Sets the default text color of this specific legend item to \a color.
12828
12829 \see setFont, QCPLegend::setTextColor
12830 */
12831 void QCPAbstractLegendItem::setTextColor(const QColor &color) {
12832 mTextColor = color;
12833 }
12834
12835 /*!
12836 When this legend item is selected, \a font is used to draw generic text,
12837 instead of the normal font set with \ref setFont.
12838
12839 \see setFont, QCPLegend::setSelectedFont
12840 */
12841 void QCPAbstractLegendItem::setSelectedFont(const QFont &font) {
12842 mSelectedFont = font;
12843 }
12844
12845 /*!
12846 When this legend item is selected, \a color is used to draw generic text,
12847 instead of the normal color set with \ref setTextColor.
12848
12849 \see setTextColor, QCPLegend::setSelectedTextColor
12850 */
12851 void QCPAbstractLegendItem::setSelectedTextColor(const QColor &color) {
12852 mSelectedTextColor = color;
12853 }
12854
12855 /*!
12856 Sets whether this specific legend item is selectable.
12857
12858 \see setSelectedParts, QCustomPlot::setInteractions
12859 */
12860 void QCPAbstractLegendItem::setSelectable(bool selectable) {
12861 if (mSelectable != selectable) {
12862 mSelectable = selectable;
12863 emit selectableChanged(mSelectable);
12864 }
12865 }
12866
12867 /*!
12868 Sets whether this specific legend item is selected.
12869
12870 It is possible to set the selection state of this item by calling this
12871 function directly, even if setSelectable is set to false.
12872
12873 \see setSelectableParts, QCustomPlot::setInteractions
12874 */
12875 void QCPAbstractLegendItem::setSelected(bool selected) {
12876 if (mSelected != selected) {
12877 mSelected = selected;
12878 emit selectionChanged(mSelected);
12879 }
12880 }
12881
12882 /* inherits documentation from base class */
12883 double QCPAbstractLegendItem::selectTest(const QPointF &pos,
12884 bool onlySelectable,
12885 QVariant *details) const {
12886 Q_UNUSED(details)
12887 if (!mParentPlot) return -1;
12888 if (onlySelectable &&
12889 (!mSelectable ||
12890 !mParentLegend->selectableParts().testFlag(QCPLegend::spItems)))
12891 return -1;
12892
12893 if (mRect.contains(pos.toPoint()))
12894 return mParentPlot->selectionTolerance() * 0.99;
12895 else
12896 return -1;
12897 }
12898
12899 /* inherits documentation from base class */
12900 void QCPAbstractLegendItem::applyDefaultAntialiasingHint(
12901 QCPPainter *painter) const {
12902 applyAntialiasingHint(painter, mAntialiased, QCP::aeLegendItems);
12903 }
12904
12905 /* inherits documentation from base class */
12906 QRect QCPAbstractLegendItem::clipRect() const { return mOuterRect; }
12907
12908 /* inherits documentation from base class */
12909 void QCPAbstractLegendItem::selectEvent(QMouseEvent *event, bool additive,
12910 const QVariant &details,
12911 bool *selectionStateChanged) {
12912 Q_UNUSED(event)
12913 Q_UNUSED(details)
12914 if (mSelectable &&
12915 mParentLegend->selectableParts().testFlag(QCPLegend::spItems)) {
12916 bool selBefore = mSelected;
12917 setSelected(additive ? !mSelected : true);
12918 if (selectionStateChanged) *selectionStateChanged = mSelected != selBefore;
12919 }
12920 }
12921
12922 /* inherits documentation from base class */
12923 void QCPAbstractLegendItem::deselectEvent(bool *selectionStateChanged) {
12924 if (mSelectable &&
12925 mParentLegend->selectableParts().testFlag(QCPLegend::spItems)) {
12926 bool selBefore = mSelected;
12927 setSelected(false);
12928 if (selectionStateChanged) *selectionStateChanged = mSelected != selBefore;
12929 }
12930 }
12931
12932 ////////////////////////////////////////////////////////////////////////////////////////////////////
12933 //////////////////// QCPPlottableLegendItem
12934 ////////////////////////////////////////////////////////////////////////////////////////////////////
12935
12936 /*! \class QCPPlottableLegendItem
12937 \brief A legend item representing a plottable with an icon and the plottable
12938 name.
12939
12940 This is the standard legend item for plottables. It displays an icon of the
12941 plottable next to the plottable name. The icon is drawn by the respective
12942 plottable itself (\ref QCPAbstractPlottable::drawLegendIcon), and tries to
12943 give an intuitive symbol for the plottable. For example, the QCPGraph draws a
12944 centered horizontal line and/or a single scatter point in the middle.
12945
12946 Legend items of this type are always associated with one plottable
12947 (retrievable via the plottable() function and settable with the constructor).
12948 You may change the font of the plottable name with \ref setFont. Icon padding
12949 and border pen is taken from the parent QCPLegend, see \ref
12950 QCPLegend::setIconBorderPen and \ref QCPLegend::setIconTextPadding.
12951
12952 The function \ref QCPAbstractPlottable::addToLegend/\ref
12953 QCPAbstractPlottable::removeFromLegend creates/removes legend items of this
12954 type in the default implementation. However, these functions may be
12955 reimplemented such that a different kind of legend item (e.g a direct subclass
12956 of QCPAbstractLegendItem) is used for that plottable.
12957
12958 Since QCPLegend is based on QCPLayoutGrid, a legend item itself is just a
12959 subclass of QCPLayoutElement. While it could be added to a legend (or any
12960 other layout) via the normal layout interface, QCPLegend has specialized
12961 functions for handling legend items conveniently, see the documentation of
12962 \ref QCPLegend.
12963 */
12964
12965 /*!
12966 Creates a new legend item associated with \a plottable.
12967
12968 Once it's created, it can be added to the legend via \ref QCPLegend::addItem.
12969
12970 A more convenient way of adding/removing a plottable to/from the legend is via
12971 the functions \ref QCPAbstractPlottable::addToLegend and \ref
12972 QCPAbstractPlottable::removeFromLegend.
12973 */
12974 QCPPlottableLegendItem::QCPPlottableLegendItem(QCPLegend *parent,
12975 QCPAbstractPlottable *plottable)
12976 : QCPAbstractLegendItem(parent), mPlottable(plottable) {}
12977
12978 /*! \internal
12979
12980 Returns the pen that shall be used to draw the icon border, taking into
12981 account the selection state of this item.
12982 */
12983 QPen QCPPlottableLegendItem::getIconBorderPen() const {
12984 return mSelected ? mParentLegend->selectedIconBorderPen()
12985 : mParentLegend->iconBorderPen();
12986 }
12987
12988 /*! \internal
12989
12990 Returns the text color that shall be used to draw text, taking into account
12991 the selection state of this item.
12992 */
12993 QColor QCPPlottableLegendItem::getTextColor() const {
12994 return mSelected ? mSelectedTextColor : mTextColor;
12995 }
12996
12997 /*! \internal
12998
12999 Returns the font that shall be used to draw text, taking into account the
13000 selection state of this item.
13001 */
13002 QFont QCPPlottableLegendItem::getFont() const {
13003 return mSelected ? mSelectedFont : mFont;
13004 }
13005
13006 /*! \internal
13007
13008 Draws the item with \a painter. The size and position of the drawn legend item
13009 is defined by the parent layout (typically a \ref QCPLegend) and the \ref
13010 minimumSizeHint and \ref maximumSizeHint of this legend item.
13011 */
13012 void QCPPlottableLegendItem::draw(QCPPainter *painter) {
13013 if (!mPlottable) return;
13014 painter->setFont(getFont());
13015 painter->setPen(QPen(getTextColor()));
13016 QSizeF iconSize = mParentLegend->iconSize();
13017 QRectF textRect = painter->fontMetrics().boundingRect(
13018 0, 0, 0, iconSize.height(), Qt::TextDontClip, mPlottable->name());
13019 QRectF iconRect(mRect.topLeft(), iconSize);
13020 int textHeight = qMax(
13021 textRect.height(),
13022 iconSize.height()); // if text has smaller height than icon, center text
13023 // vertically in icon height, else align tops
13024 painter->drawText(
13025 mRect.x() + iconSize.width() + mParentLegend->iconTextPadding(),
13026 mRect.y(), textRect.width(), textHeight, Qt::TextDontClip,
13027 mPlottable->name());
13028 // draw icon:
13029 painter->save();
13030 painter->setClipRect(iconRect, Qt::IntersectClip);
13031 mPlottable->drawLegendIcon(painter, iconRect);
13032 painter->restore();
13033 // draw icon border:
13034 if (getIconBorderPen().style() != Qt::NoPen) {
13035 painter->setPen(getIconBorderPen());
13036 painter->setBrush(Qt::NoBrush);
13037 painter->drawRect(iconRect);
13038 }
13039 }
13040
13041 /*! \internal
13042
13043 Calculates and returns the size of this item. This includes the icon, the text
13044 and the padding in between.
13045 */
13046 QSize QCPPlottableLegendItem::minimumSizeHint() const {
13047 if (!mPlottable) return QSize();
13048 QSize result(0, 0);
13049 QRect textRect;
13050 QFontMetrics fontMetrics(getFont());
13051 QSize iconSize = mParentLegend->iconSize();
13052 textRect = fontMetrics.boundingRect(0, 0, 0, iconSize.height(),
13053 Qt::TextDontClip, mPlottable->name());
13054 result.setWidth(iconSize.width() + mParentLegend->iconTextPadding() +
13055 textRect.width() + mMargins.left() + mMargins.right());
13056 result.setHeight(qMax(textRect.height(), iconSize.height()) + mMargins.top() +
13057 mMargins.bottom());
13058 return result;
13059 }
13060
13061 ////////////////////////////////////////////////////////////////////////////////////////////////////
13062 //////////////////// QCPLegend
13063 ////////////////////////////////////////////////////////////////////////////////////////////////////
13064
13065 /*! \class QCPLegend
13066 \brief Manages a legend inside a QCustomPlot.
13067
13068 A legend is a small box somewhere in the plot which lists plottables with
13069 their name and icon.
13070
13071 Normally, the legend is populated by calling \ref
13072 QCPAbstractPlottable::addToLegend. The respective legend item can be removed
13073 with \ref QCPAbstractPlottable::removeFromLegend. However, QCPLegend also
13074 offers an interface to add and manipulate legend items directly: \ref item,
13075 \ref itemWithPlottable, \ref itemCount, \ref addItem, \ref removeItem, etc.
13076
13077 The QCPLegend derives from QCPLayoutGrid and as such can be placed in any
13078 position a QCPLayoutElement may be positioned. The legend items are themselves
13079 QCPLayoutElements which are placed in the grid layout of the legend. QCPLegend
13080 only adds an interface specialized for handling child elements of type
13081 QCPAbstractLegendItem, as mentioned above. In principle, any other layout
13082 elements may also be added to a legend via the normal \ref QCPLayoutGrid
13083 interface. However, the QCPAbstractLegendItem-Interface will ignore those
13084 elements (e.g. \ref itemCount will only return the number of items with
13085 QCPAbstractLegendItems type).
13086
13087 By default, every QCustomPlot has one legend (QCustomPlot::legend) which is
13088 placed in the inset layout of the main axis rect (\ref
13089 QCPAxisRect::insetLayout). To move the legend to another position inside the
13090 axis rect, use the methods of the \ref QCPLayoutInset. To move the legend
13091 outside of the axis rect, place it anywhere else with the
13092 QCPLayout/QCPLayoutElement interface.
13093 */
13094
13095 /* start of documentation of signals */
13096
13097 /*! \fn void QCPLegend::selectionChanged(QCPLegend::SelectableParts selection);
13098
13099 This signal is emitted when the selection state of this legend has changed.
13100
13101 \see setSelectedParts, setSelectableParts
13102 */
13103
13104 /* end of documentation of signals */
13105
13106 /*!
13107 Constructs a new QCPLegend instance with \a parentPlot as the containing plot
13108 and default values.
13109
13110 Note that by default, QCustomPlot already contains a legend ready to be used
13111 as QCustomPlot::legend
13112 */
13113 QCPLegend::QCPLegend() {
13114 setRowSpacing(0);
13115 setColumnSpacing(10);
13116 setMargins(QMargins(2, 3, 2, 2));
13117 setAntialiased(false);
13118 setIconSize(32, 18);
13119
13120 setIconTextPadding(7);
13121
13122 setSelectableParts(spLegendBox | spItems);
13123 setSelectedParts(spNone);
13124
13125 setBorderPen(QPen(Qt::black));
13126 setSelectedBorderPen(QPen(Qt::blue, 2));
13127 setIconBorderPen(Qt::NoPen);
13128 setSelectedIconBorderPen(QPen(Qt::blue, 2));
13129 setBrush(Qt::white);
13130 setSelectedBrush(Qt::white);
13131 setTextColor(Qt::black);
13132 setSelectedTextColor(Qt::blue);
13133 }
13134
13135 QCPLegend::~QCPLegend() {
13136 clearItems();
13137 if (qobject_cast<QCustomPlot *>(
13138 mParentPlot)) // make sure this isn't called from QObject dtor when
13139 // QCustomPlot is already destructed (happens when the
13140 // legend is not in any layout and thus QObject-child
13141 // of QCustomPlot)
13142 mParentPlot->legendRemoved(this);
13143 }
13144
13145 /* no doc for getter, see setSelectedParts */
13146 QCPLegend::SelectableParts QCPLegend::selectedParts() const {
13147 // check whether any legend elements selected, if yes, add spItems to return
13148 // value
13149 bool hasSelectedItems = false;
13150 for (int i = 0; i < itemCount(); ++i) {
13151 if (item(i) && item(i)->selected()) {
13152 hasSelectedItems = true;
13153 break;
13154 }
13155 }
13156 if (hasSelectedItems)
13157 return mSelectedParts | spItems;
13158 else
13159 return mSelectedParts & ~spItems;
13160 }
13161
13162 /*!
13163 Sets the pen, the border of the entire legend is drawn with.
13164 */
13165 void QCPLegend::setBorderPen(const QPen &pen) { mBorderPen = pen; }
13166
13167 /*!
13168 Sets the brush of the legend background.
13169 */
13170 void QCPLegend::setBrush(const QBrush &brush) { mBrush = brush; }
13171
13172 /*!
13173 Sets the default font of legend text. Legend items that draw text (e.g. the
13174 name of a graph) will use this font by default. However, a different font can
13175 be specified on a per-item-basis by accessing the specific legend item.
13176
13177 This function will also set \a font on all already existing legend items.
13178
13179 \see QCPAbstractLegendItem::setFont
13180 */
13181 void QCPLegend::setFont(const QFont &font) {
13182 mFont = font;
13183 for (int i = 0; i < itemCount(); ++i) {
13184 if (item(i)) item(i)->setFont(mFont);
13185 }
13186 }
13187
13188 /*!
13189 Sets the default color of legend text. Legend items that draw text (e.g. the
13190 name of a graph) will use this color by default. However, a different colors
13191 can be specified on a per-item-basis by accessing the specific legend item.
13192
13193 This function will also set \a color on all already existing legend items.
13194
13195 \see QCPAbstractLegendItem::setTextColor
13196 */
13197 void QCPLegend::setTextColor(const QColor &color) {
13198 mTextColor = color;
13199 for (int i = 0; i < itemCount(); ++i) {
13200 if (item(i)) item(i)->setTextColor(color);
13201 }
13202 }
13203
13204 /*!
13205 Sets the size of legend icons. Legend items that draw an icon (e.g. a visual
13206 representation of the graph) will use this size by default.
13207 */
13208 void QCPLegend::setIconSize(const QSize &size) { mIconSize = size; }
13209
13210 /*! \overload
13211 */
13212 void QCPLegend::setIconSize(int width, int height) {
13213 mIconSize.setWidth(width);
13214 mIconSize.setHeight(height);
13215 }
13216
13217 /*!
13218 Sets the horizontal space in pixels between the legend icon and the text next
13219 to it. Legend items that draw an icon (e.g. a visual representation of the
13220 graph) and text (e.g. the name of the graph) will use this space by default.
13221 */
13222 void QCPLegend::setIconTextPadding(int padding) { mIconTextPadding = padding; }
13223
13224 /*!
13225 Sets the pen used to draw a border around each legend icon. Legend items that
13226 draw an icon (e.g. a visual representation of the graph) will use this pen by
13227 default.
13228
13229 If no border is wanted, set this to \a Qt::NoPen.
13230 */
13231 void QCPLegend::setIconBorderPen(const QPen &pen) { mIconBorderPen = pen; }
13232
13233 /*!
13234 Sets whether the user can (de-)select the parts in \a selectable by clicking
13235 on the QCustomPlot surface. (When \ref QCustomPlot::setInteractions contains
13236 \ref QCP::iSelectLegend.)
13237
13238 However, even when \a selectable is set to a value not allowing the selection
13239 of a specific part, it is still possible to set the selection of this part
13240 manually, by calling \ref setSelectedParts directly.
13241
13242 \see SelectablePart, setSelectedParts
13243 */
13244 void QCPLegend::setSelectableParts(const SelectableParts &selectable) {
13245 if (mSelectableParts != selectable) {
13246 mSelectableParts = selectable;
13247 emit selectableChanged(mSelectableParts);
13248 }
13249 }
13250
13251 /*!
13252 Sets the selected state of the respective legend parts described by \ref
13253 SelectablePart. When a part is selected, it uses a different pen/font and
13254 brush. If some legend items are selected and \a selected doesn't contain \ref
13255 spItems, those items become deselected.
13256
13257 The entire selection mechanism is handled automatically when \ref
13258 QCustomPlot::setInteractions contains iSelectLegend. You only need to call
13259 this function when you wish to change the selection state manually.
13260
13261 This function can change the selection state of a part even when \ref
13262 setSelectableParts was set to a value that actually excludes the part.
13263
13264 emits the \ref selectionChanged signal when \a selected is different from the
13265 previous selection state.
13266
13267 Note that it doesn't make sense to set the selected state \ref spItems here
13268 when it wasn't set before, because there's no way to specify which exact items
13269 to newly select. Do this by calling \ref QCPAbstractLegendItem::setSelected
13270 directly on the legend item you wish to select.
13271
13272 \see SelectablePart, setSelectableParts, selectTest, setSelectedBorderPen,
13273 setSelectedIconBorderPen, setSelectedBrush, setSelectedFont
13274 */
13275 void QCPLegend::setSelectedParts(const SelectableParts &selected) {
13276 SelectableParts newSelected = selected;
13277 mSelectedParts = this->selectedParts(); // update mSelectedParts in case item
13278 // selection changed
13279
13280 if (mSelectedParts != newSelected) {
13281 if (!mSelectedParts.testFlag(spItems) &&
13282 newSelected.testFlag(
13283 spItems)) // attempt to set spItems flag (can't do that)
13284 {
13285 qDebug() << Q_FUNC_INFO
13286 << "spItems flag can not be set, it can only be unset with this "
13287 "function";
13288 newSelected &= ~spItems;
13289 }
13290 if (mSelectedParts.testFlag(spItems) &&
13291 !newSelected.testFlag(
13292 spItems)) // spItems flag was unset, so clear item selection
13293 {
13294 for (int i = 0; i < itemCount(); ++i) {
13295 if (item(i)) item(i)->setSelected(false);
13296 }
13297 }
13298 mSelectedParts = newSelected;
13299 emit selectionChanged(mSelectedParts);
13300 }
13301 }
13302
13303 /*!
13304 When the legend box is selected, this pen is used to draw the border instead
13305 of the normal pen set via \ref setBorderPen.
13306
13307 \see setSelectedParts, setSelectableParts, setSelectedBrush
13308 */
13309 void QCPLegend::setSelectedBorderPen(const QPen &pen) {
13310 mSelectedBorderPen = pen;
13311 }
13312
13313 /*!
13314 Sets the pen legend items will use to draw their icon borders, when they are
13315 selected.
13316
13317 \see setSelectedParts, setSelectableParts, setSelectedFont
13318 */
13319 void QCPLegend::setSelectedIconBorderPen(const QPen &pen) {
13320 mSelectedIconBorderPen = pen;
13321 }
13322
13323 /*!
13324 When the legend box is selected, this brush is used to draw the legend
13325 background instead of the normal brush set via \ref setBrush.
13326
13327 \see setSelectedParts, setSelectableParts, setSelectedBorderPen
13328 */
13329 void QCPLegend::setSelectedBrush(const QBrush &brush) {
13330 mSelectedBrush = brush;
13331 }
13332
13333 /*!
13334 Sets the default font that is used by legend items when they are selected.
13335
13336 This function will also set \a font on all already existing legend items.
13337
13338 \see setFont, QCPAbstractLegendItem::setSelectedFont
13339 */
13340 void QCPLegend::setSelectedFont(const QFont &font) {
13341 mSelectedFont = font;
13342 for (int i = 0; i < itemCount(); ++i) {
13343 if (item(i)) item(i)->setSelectedFont(font);
13344 }
13345 }
13346
13347 /*!
13348 Sets the default text color that is used by legend items when they are
13349 selected.
13350
13351 This function will also set \a color on all already existing legend items.
13352
13353 \see setTextColor, QCPAbstractLegendItem::setSelectedTextColor
13354 */
13355 void QCPLegend::setSelectedTextColor(const QColor &color) {
13356 mSelectedTextColor = color;
13357 for (int i = 0; i < itemCount(); ++i) {
13358 if (item(i)) item(i)->setSelectedTextColor(color);
13359 }
13360 }
13361
13362 /*!
13363 Returns the item with index \a i.
13364
13365 \see itemCount
13366 */
13367 QCPAbstractLegendItem *QCPLegend::item(int index) const {
13368 return qobject_cast<QCPAbstractLegendItem *>(elementAt(index));
13369 }
13370
13371 /*!
13372 Returns the QCPPlottableLegendItem which is associated with \a plottable (e.g.
13373 a \ref QCPGraph*). If such an item isn't in the legend, returns 0.
13374
13375 \see hasItemWithPlottable
13376 */
13377 QCPPlottableLegendItem *QCPLegend::itemWithPlottable(
13378 const QCPAbstractPlottable *plottable) const {
13379 for (int i = 0; i < itemCount(); ++i) {
13380 if (QCPPlottableLegendItem *pli =
13381 qobject_cast<QCPPlottableLegendItem *>(item(i))) {
13382 if (pli->plottable() == plottable) return pli;
13383 }
13384 }
13385 return 0;
13386 }
13387
13388 /*!
13389 Returns the number of items currently in the legend.
13390 \see item
13391 */
13392 int QCPLegend::itemCount() const { return elementCount(); }
13393
13394 /*!
13395 Returns whether the legend contains \a itm.
13396 */
13397 bool QCPLegend::hasItem(QCPAbstractLegendItem *item) const {
13398 for (int i = 0; i < itemCount(); ++i) {
13399 if (item == this->item(i)) return true;
13400 }
13401 return false;
13402 }
13403
13404 /*!
13405 Returns whether the legend contains a QCPPlottableLegendItem which is
13406 associated with \a plottable (e.g. a \ref QCPGraph*). If such an item isn't in
13407 the legend, returns false.
13408
13409 \see itemWithPlottable
13410 */
13411 bool QCPLegend::hasItemWithPlottable(
13412 const QCPAbstractPlottable *plottable) const {
13413 return itemWithPlottable(plottable);
13414 }
13415
13416 /*!
13417 Adds \a item to the legend, if it's not present already.
13418
13419 Returns true on sucess, i.e. if the item wasn't in the list already and has
13420 been successfuly added.
13421
13422 The legend takes ownership of the item.
13423 */
13424 bool QCPLegend::addItem(QCPAbstractLegendItem *item) {
13425 if (!hasItem(item)) {
13426 return addElement(rowCount(), 0, item);
13427 } else
13428 return false;
13429 }
13430
13431 /*!
13432 Removes the item with index \a index from the legend.
13433
13434 Returns true, if successful.
13435
13436 \see itemCount, clearItems
13437 */
13438 bool QCPLegend::removeItem(int index) {
13439 if (QCPAbstractLegendItem *ali = item(index)) {
13440 bool success = remove(ali);
13441 simplify();
13442 return success;
13443 } else
13444 return false;
13445 }
13446
13447 /*! \overload
13448
13449 Removes \a item from the legend.
13450
13451 Returns true, if successful.
13452
13453 \see clearItems
13454 */
13455 bool QCPLegend::removeItem(QCPAbstractLegendItem *item) {
13456 bool success = remove(item);
13457 simplify();
13458 return success;
13459 }
13460
13461 /*!
13462 Removes all items from the legend.
13463 */
13464 void QCPLegend::clearItems() {
13465 for (int i = itemCount() - 1; i >= 0; --i) removeItem(i);
13466 }
13467
13468 /*!
13469 Returns the legend items that are currently selected. If no items are
13470 selected, the list is empty.
13471
13472 \see QCPAbstractLegendItem::setSelected, setSelectable
13473 */
13474 QList<QCPAbstractLegendItem *> QCPLegend::selectedItems() const {
13475 QList<QCPAbstractLegendItem *> result;
13476 for (int i = 0; i < itemCount(); ++i) {
13477 if (QCPAbstractLegendItem *ali = item(i)) {
13478 if (ali->selected()) result.append(ali);
13479 }
13480 }
13481 return result;
13482 }
13483
13484 /*! \internal
13485
13486 A convenience function to easily set the QPainter::Antialiased hint on the
13487 provided \a painter before drawing main legend elements.
13488
13489 This is the antialiasing state the painter passed to the \ref draw method is
13490 in by default.
13491
13492 This function takes into account the local setting of the antialiasing flag as
13493 well as the overrides set with \ref QCustomPlot::setAntialiasedElements and
13494 \ref QCustomPlot::setNotAntialiasedElements.
13495
13496 \see setAntialiased
13497 */
13498 void QCPLegend::applyDefaultAntialiasingHint(QCPPainter *painter) const {
13499 applyAntialiasingHint(painter, mAntialiased, QCP::aeLegend);
13500 }
13501
13502 /*! \internal
13503
13504 Returns the pen used to paint the border of the legend, taking into account
13505 the selection state of the legend box.
13506 */
13507 QPen QCPLegend::getBorderPen() const {
13508 return mSelectedParts.testFlag(spLegendBox) ? mSelectedBorderPen : mBorderPen;
13509 }
13510
13511 /*! \internal
13512
13513 Returns the brush used to paint the background of the legend, taking into
13514 account the selection state of the legend box.
13515 */
13516 QBrush QCPLegend::getBrush() const {
13517 return mSelectedParts.testFlag(spLegendBox) ? mSelectedBrush : mBrush;
13518 }
13519
13520 /*! \internal
13521
13522 Draws the legend box with the provided \a painter. The individual legend items
13523 are layerables themselves, thus are drawn independently.
13524 */
13525 void QCPLegend::draw(QCPPainter *painter) {
13526 // draw background rect:
13527 painter->setBrush(getBrush());
13528 painter->setPen(getBorderPen());
13529 painter->drawRect(mOuterRect);
13530 }
13531
13532 /* inherits documentation from base class */
13533 double QCPLegend::selectTest(const QPointF &pos, bool onlySelectable,
13534 QVariant *details) const {
13535 if (!mParentPlot) return -1;
13536 if (onlySelectable && !mSelectableParts.testFlag(spLegendBox)) return -1;
13537
13538 if (mOuterRect.contains(pos.toPoint())) {
13539 if (details) details->setValue(spLegendBox);
13540 return mParentPlot->selectionTolerance() * 0.99;
13541 }
13542 return -1;
13543 }
13544
13545 /* inherits documentation from base class */
13546 void QCPLegend::selectEvent(QMouseEvent *event, bool additive,
13547 const QVariant &details,
13548 bool *selectionStateChanged) {
13549 Q_UNUSED(event)
13550 mSelectedParts = selectedParts(); // in case item selection has changed
13551 if (details.value<SelectablePart>() == spLegendBox &&
13552 mSelectableParts.testFlag(spLegendBox)) {
13553 SelectableParts selBefore = mSelectedParts;
13554 setSelectedParts(
13555 additive ? mSelectedParts ^ spLegendBox
13556 : mSelectedParts |
13557 spLegendBox); // no need to unset spItems in !additive
13558 // case, because they will be deselected
13559 // by QCustomPlot (they're normal
13560 // QCPLayerables with own deselectEvent)
13561 if (selectionStateChanged)
13562 *selectionStateChanged = mSelectedParts != selBefore;
13563 }
13564 }
13565
13566 /* inherits documentation from base class */
13567 void QCPLegend::deselectEvent(bool *selectionStateChanged) {
13568 mSelectedParts = selectedParts(); // in case item selection has changed
13569 if (mSelectableParts.testFlag(spLegendBox)) {
13570 SelectableParts selBefore = mSelectedParts;
13571 setSelectedParts(selectedParts() & ~spLegendBox);
13572 if (selectionStateChanged)
13573 *selectionStateChanged = mSelectedParts != selBefore;
13574 }
13575 }
13576
13577 /* inherits documentation from base class */
13578 QCP::Interaction QCPLegend::selectionCategory() const {
13579 return QCP::iSelectLegend;
13580 }
13581
13582 /* inherits documentation from base class */
13583 QCP::Interaction QCPAbstractLegendItem::selectionCategory() const {
13584 return QCP::iSelectLegend;
13585 }
13586
13587 /* inherits documentation from base class */
13588 void QCPLegend::parentPlotInitialized(QCustomPlot *parentPlot) {
13589 Q_UNUSED(parentPlot)
13590 }
13591
13592 ////////////////////////////////////////////////////////////////////////////////////////////////////
13593 //////////////////// QCPPlotTitle
13594 ////////////////////////////////////////////////////////////////////////////////////////////////////
13595
13596 /*! \class QCPPlotTitle
13597 \brief A layout element displaying a plot title text
13598
13599 The text may be specified with \ref setText, theformatting can be controlled
13600 with \ref setFont and \ref setTextColor.
13601
13602 A plot title can be added as follows:
13603 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpplottitle-creation
13604
13605 Since a plot title is a common requirement, QCustomPlot offers specialized
13606 selection signals for easy interaction with QCPPlotTitle. If a layout element
13607 of type QCPPlotTitle is clicked, the signal \ref QCustomPlot::titleClick is
13608 emitted. A double click emits the \ref QCustomPlot::titleDoubleClick signal.
13609 */
13610
13611 /* start documentation of signals */
13612
13613 /*! \fn void QCPPlotTitle::selectionChanged(bool selected)
13614
13615 This signal is emitted when the selection state has changed to \a selected,
13616 either by user interaction or by a direct call to \ref setSelected.
13617
13618 \see setSelected, setSelectable
13619 */
13620
13621 /* end documentation of signals */
13622
13623 /*!
13624 Creates a new QCPPlotTitle instance and sets default values. The initial text
13625 is empty (\ref setText).
13626
13627 To set the title text in the constructor, rather use \ref
13628 QCPPlotTitle(QCustomPlot *parentPlot, const QString &text).
13629 */
13630 QCPPlotTitle::QCPPlotTitle(QCustomPlot *parentPlot)
13631 : QCPLayoutElement(parentPlot),
13632 mFont(QFont(QLatin1String("sans serif"), 13 * 1.5, QFont::Bold)),
13633 mTextColor(Qt::black),
13634 mSelectedFont(QFont(QLatin1String("sans serif"), 13 * 1.6, QFont::Bold)),
13635 mSelectedTextColor(Qt::blue),
13636 mSelectable(false),
13637 mSelected(false) {
13638 if (parentPlot) {
13639 setLayer(parentPlot->currentLayer());
13640 mFont = QFont(parentPlot->font().family(),
13641 parentPlot->font().pointSize() * 1.5, QFont::Bold);
13642 mSelectedFont = QFont(parentPlot->font().family(),
13643 parentPlot->font().pointSize() * 1.6, QFont::Bold);
13644 }
13645 setMargins(QMargins(5, 5, 5, 0));
13646 }
13647
13648 /*! \overload
13649
13650 Creates a new QCPPlotTitle instance and sets default values. The initial text
13651 is set to \a text.
13652 */
13653 QCPPlotTitle::QCPPlotTitle(QCustomPlot *parentPlot, const QString &text)
13654 : QCPLayoutElement(parentPlot),
13655 mText(text),
13656 mFont(QFont(parentPlot->font().family(),
13657 parentPlot->font().pointSize() * 1.5, QFont::Bold)),
13658 mTextColor(Qt::black),
13659 mSelectedFont(QFont(parentPlot->font().family(),
13660 parentPlot->font().pointSize() * 1.6, QFont::Bold)),
13661 mSelectedTextColor(Qt::blue),
13662 mSelectable(false),
13663 mSelected(false) {
13664 setLayer(QLatin1String("axes"));
13665 setMargins(QMargins(5, 5, 5, 0));
13666 }
13667
13668 /*!
13669 Sets the text that will be displayed to \a text. Multiple lines can be created
13670 by insertion of "\n".
13671
13672 \see setFont, setTextColor
13673 */
13674 void QCPPlotTitle::setText(const QString &text) { mText = text; }
13675
13676 /*!
13677 Sets the \a font of the title text.
13678
13679 \see setTextColor, setSelectedFont
13680 */
13681 void QCPPlotTitle::setFont(const QFont &font) { mFont = font; }
13682
13683 /*!
13684 Sets the \a color of the title text.
13685
13686 \see setFont, setSelectedTextColor
13687 */
13688 void QCPPlotTitle::setTextColor(const QColor &color) { mTextColor = color; }
13689
13690 /*!
13691 Sets the \a font of the title text that will be used if the plot title is
13692 selected (\ref setSelected).
13693
13694 \see setFont
13695 */
13696 void QCPPlotTitle::setSelectedFont(const QFont &font) { mSelectedFont = font; }
13697
13698 /*!
13699 Sets the \a color of the title text that will be used if the plot title is
13700 selected (\ref setSelected).
13701
13702 \see setTextColor
13703 */
13704 void QCPPlotTitle::setSelectedTextColor(const QColor &color) {
13705 mSelectedTextColor = color;
13706 }
13707
13708 /*!
13709 Sets whether the user may select this plot title to \a selectable.
13710
13711 Note that even when \a selectable is set to <tt>false</tt>, the selection
13712 state may be changed programmatically via \ref setSelected.
13713 */
13714 void QCPPlotTitle::setSelectable(bool selectable) {
13715 if (mSelectable != selectable) {
13716 mSelectable = selectable;
13717 emit selectableChanged(mSelectable);
13718 }
13719 }
13720
13721 /*!
13722 Sets the selection state of this plot title to \a selected. If the selection
13723 has changed, \ref selectionChanged is emitted.
13724
13725 Note that this function can change the selection state independently of the
13726 current \ref setSelectable state.
13727 */
13728 void QCPPlotTitle::setSelected(bool selected) {
13729 if (mSelected != selected) {
13730 mSelected = selected;
13731 emit selectionChanged(mSelected);
13732 }
13733 }
13734
13735 /* inherits documentation from base class */
13736 void QCPPlotTitle::applyDefaultAntialiasingHint(QCPPainter *painter) const {
13737 applyAntialiasingHint(painter, mAntialiased, QCP::aeNone);
13738 }
13739
13740 /* inherits documentation from base class */
13741 void QCPPlotTitle::draw(QCPPainter *painter) {
13742 painter->setFont(mainFont());
13743 painter->setPen(QPen(mainTextColor()));
13744 painter->drawText(mRect, Qt::AlignCenter, mText, &mTextBoundingRect);
13745 }
13746
13747 /* inherits documentation from base class */
13748 QSize QCPPlotTitle::minimumSizeHint() const {
13749 QFontMetrics metrics(mFont);
13750 QSize result =
13751 metrics.boundingRect(0, 0, 0, 0, Qt::AlignCenter, mText).size();
13752 result.rwidth() += mMargins.left() + mMargins.right();
13753 result.rheight() += mMargins.top() + mMargins.bottom();
13754 return result;
13755 }
13756
13757 /* inherits documentation from base class */
13758 QSize QCPPlotTitle::maximumSizeHint() const {
13759 QFontMetrics metrics(mFont);
13760 QSize result =
13761 metrics.boundingRect(0, 0, 0, 0, Qt::AlignCenter, mText).size();
13762 result.rheight() += mMargins.top() + mMargins.bottom();
13763 result.setWidth(QWIDGETSIZE_MAX);
13764 return result;
13765 }
13766
13767 /* inherits documentation from base class */
13768 void QCPPlotTitle::selectEvent(QMouseEvent *event, bool additive,
13769 const QVariant &details,
13770 bool *selectionStateChanged) {
13771 Q_UNUSED(event)
13772 Q_UNUSED(details)
13773 if (mSelectable) {
13774 bool selBefore = mSelected;
13775 setSelected(additive ? !mSelected : true);
13776 if (selectionStateChanged) *selectionStateChanged = mSelected != selBefore;
13777 }
13778 }
13779
13780 /* inherits documentation from base class */
13781 void QCPPlotTitle::deselectEvent(bool *selectionStateChanged) {
13782 if (mSelectable) {
13783 bool selBefore = mSelected;
13784 setSelected(false);
13785 if (selectionStateChanged) *selectionStateChanged = mSelected != selBefore;
13786 }
13787 }
13788
13789 /* inherits documentation from base class */
13790 double QCPPlotTitle::selectTest(const QPointF &pos, bool onlySelectable,
13791 QVariant *details) const {
13792 Q_UNUSED(details)
13793 if (onlySelectable && !mSelectable) return -1;
13794
13795 if (mTextBoundingRect.contains(pos.toPoint()))
13796 return mParentPlot->selectionTolerance() * 0.99;
13797 else
13798 return -1;
13799 }
13800
13801 /*! \internal
13802
13803 Returns the main font to be used. This is mSelectedFont if \ref setSelected is
13804 set to <tt>true</tt>, else mFont is returned.
13805 */
13806 QFont QCPPlotTitle::mainFont() const {
13807 return mSelected ? mSelectedFont : mFont;
13808 }
13809
13810 /*! \internal
13811
13812 Returns the main color to be used. This is mSelectedTextColor if \ref
13813 setSelected is set to <tt>true</tt>, else mTextColor is returned.
13814 */
13815 QColor QCPPlotTitle::mainTextColor() const {
13816 return mSelected ? mSelectedTextColor : mTextColor;
13817 }
13818
13819 ////////////////////////////////////////////////////////////////////////////////////////////////////
13820 //////////////////// QCPColorScale
13821 ////////////////////////////////////////////////////////////////////////////////////////////////////
13822
13823 /*! \class QCPColorScale
13824 \brief A color scale for use with color coding data such as QCPColorMap
13825
13826 This layout element can be placed on the plot to correlate a color gradient
13827 with data values. It is usually used in combination with one or multiple \ref
13828 QCPColorMap "QCPColorMaps".
13829
13830 \image html QCPColorScale.png
13831
13832 The color scale can be either horizontal or vertical, as shown in the image
13833 above. The orientation and the side where the numbers appear is controlled
13834 with \ref setType.
13835
13836 Use \ref QCPColorMap::setColorScale to connect a color map with a color scale.
13837 Once they are connected, they share their gradient, data range and data scale
13838 type (\ref setGradient, \ref setDataRange, \ref setDataScaleType). Multiple
13839 color maps may be associated with a single color scale, to make them all
13840 synchronize these properties.
13841
13842 To have finer control over the number display and axis behaviour, you can
13843 directly access the \ref axis. See the documentation of QCPAxis for details
13844 about configuring axes. For example, if you want to change the number of
13845 automatically generated ticks, call \snippet
13846 documentation/doc-code-snippets/mainwindow.cpp qcpcolorscale-autotickcount
13847
13848 Placing a color scale next to the main axis rect works like with any other
13849 layout element: \snippet documentation/doc-code-snippets/mainwindow.cpp
13850 qcpcolorscale-creation In this case we have placed it to the right of the
13851 default axis rect, so it wasn't necessary to call \ref setType, since \ref
13852 QCPAxis::atRight is already the default. The text next to the color scale can
13853 be set with \ref setLabel.
13854
13855 For optimum appearance (like in the image above), it may be desirable to line
13856 up the axis rect and the borders of the color scale. Use a \ref QCPMarginGroup
13857 to achieve this: \snippet documentation/doc-code-snippets/mainwindow.cpp
13858 qcpcolorscale-margingroup
13859
13860 Color scales are initialized with a non-zero minimum top and bottom margin
13861 (\ref setMinimumMargins), because vertical color scales are most common and
13862 the minimum top/bottom margin makes sure it keeps some distance to the
13863 top/bottom widget border. So if you change to a horizontal color scale by
13864 setting \ref setType to \ref QCPAxis::atBottom or \ref QCPAxis::atTop, you
13865 might want to also change the minimum margins accordingly, e.g.
13866 <tt>setMinimumMargins(QMargins(6, 0, 6, 0))</tt>.
13867 */
13868
13869 /* start documentation of inline functions */
13870
13871 /*! \fn QCPAxis *QCPColorScale::axis() const
13872
13873 Returns the internal \ref QCPAxis instance of this color scale. You can access
13874 it to alter the appearance and behaviour of the axis. \ref QCPColorScale
13875 duplicates some properties in its interface for convenience. Those are \ref
13876 setDataRange (\ref QCPAxis::setRange), \ref setDataScaleType (\ref
13877 QCPAxis::setScaleType), and the method \ref setLabel (\ref QCPAxis::setLabel).
13878 As they each are connected, it does not matter whether you use the method on
13879 the QCPColorScale or on its QCPAxis.
13880
13881 If the type of the color scale is changed with \ref setType, the axis returned
13882 by this method will change, too, to either the left, right, bottom or top
13883 axis, depending on which type was set.
13884 */
13885
13886 /* end documentation of signals */
13887 /* start documentation of signals */
13888
13889 /*! \fn void QCPColorScale::dataRangeChanged(QCPRange newRange);
13890
13891 This signal is emitted when the data range changes.
13892
13893 \see setDataRange
13894 */
13895
13896 /*! \fn void QCPColorScale::dataScaleTypeChanged(QCPAxis::ScaleType scaleType);
13897
13898 This signal is emitted when the data scale type changes.
13899
13900 \see setDataScaleType
13901 */
13902
13903 /*! \fn void QCPColorScale::gradientChanged(QCPColorGradient newGradient);
13904
13905 This signal is emitted when the gradient changes.
13906
13907 \see setGradient
13908 */
13909
13910 /* end documentation of signals */
13911
13912 /*!
13913 Constructs a new QCPColorScale.
13914 */
13915 QCPColorScale::QCPColorScale(QCustomPlot *parentPlot)
13916 : QCPLayoutElement(parentPlot),
13917 mType(QCPAxis::atTop), // set to atTop such that
13918 // setType(QCPAxis::atRight) below doesn't skip
13919 // work because it thinks it's already atRight
13920 mDataScaleType(QCPAxis::stLinear),
13921 mBarWidth(20),
13922 mAxisRect(new QCPColorScaleAxisRectPrivate(this)) {
13923 setMinimumMargins(QMargins(
13924 0, 6, 0, 6)); // for default right color scale types, keep some room at
13925 // bottom and top (important if no margin group is used)
13926 setType(QCPAxis::atRight);
13927 setDataRange(QCPRange(0, 6));
13928 }
13929
13930 QCPColorScale::~QCPColorScale() { delete mAxisRect; }
13931
13932 /* undocumented getter */
13933 QString QCPColorScale::label() const {
13934 if (!mColorAxis) {
13935 qDebug() << Q_FUNC_INFO << "internal color axis undefined";
13936 return QString();
13937 }
13938
13939 return mColorAxis.data()->label();
13940 }
13941
13942 /* undocumented getter */
13943 bool QCPColorScale::rangeDrag() const {
13944 if (!mAxisRect) {
13945 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
13946 return false;
13947 }
13948
13949 return mAxisRect.data()->rangeDrag().testFlag(QCPAxis::orientation(mType)) &&
13950 mAxisRect.data()->rangeDragAxis(QCPAxis::orientation(mType)) &&
13951 mAxisRect.data()
13952 ->rangeDragAxis(QCPAxis::orientation(mType))
13953 ->orientation() == QCPAxis::orientation(mType);
13954 }
13955
13956 /* undocumented getter */
13957 bool QCPColorScale::rangeZoom() const {
13958 if (!mAxisRect) {
13959 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
13960 return false;
13961 }
13962
13963 return mAxisRect.data()->rangeZoom().testFlag(QCPAxis::orientation(mType)) &&
13964 mAxisRect.data()->rangeZoomAxis(QCPAxis::orientation(mType)) &&
13965 mAxisRect.data()
13966 ->rangeZoomAxis(QCPAxis::orientation(mType))
13967 ->orientation() == QCPAxis::orientation(mType);
13968 }
13969
13970 /*!
13971 Sets at which side of the color scale the axis is placed, and thus also its
13972 orientation.
13973
13974 Note that after setting \a type to a different value, the axis returned by
13975 \ref axis() will be a different one. The new axis will adopt the following
13976 properties from the previous axis: The range, scale type, log base and label.
13977 */
13978 void QCPColorScale::setType(QCPAxis::AxisType type) {
13979 if (!mAxisRect) {
13980 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
13981 return;
13982 }
13983 if (mType != type) {
13984 mType = type;
13985 QCPRange rangeTransfer(0, 6);
13986 double logBaseTransfer = 10;
13987 QString labelTransfer;
13988 // revert some settings on old axis:
13989 if (mColorAxis) {
13990 rangeTransfer = mColorAxis.data()->range();
13991 labelTransfer = mColorAxis.data()->label();
13992 logBaseTransfer = mColorAxis.data()->scaleLogBase();
13993 mColorAxis.data()->setLabel(QString());
13994 disconnect(mColorAxis.data(), SIGNAL(rangeChanged(QCPRange)), this,
13995 SLOT(setDataRange(QCPRange)));
13996 disconnect(mColorAxis.data(),
13997 SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), this,
13998 SLOT(setDataScaleType(QCPAxis::ScaleType)));
13999 }
14000 QList<QCPAxis::AxisType> allAxisTypes =
14001 QList<QCPAxis::AxisType>() << QCPAxis::atLeft << QCPAxis::atRight
14002 << QCPAxis::atBottom << QCPAxis::atTop;
14003 foreach (QCPAxis::AxisType atype, allAxisTypes) {
14004 mAxisRect.data()->axis(atype)->setTicks(atype == mType);
14005 mAxisRect.data()->axis(atype)->setTickLabels(atype == mType);
14006 }
14007 // set new mColorAxis pointer:
14008 mColorAxis = mAxisRect.data()->axis(mType);
14009 // transfer settings to new axis:
14010 mColorAxis.data()->setRange(
14011 rangeTransfer); // transfer range of old axis to new one (necessary if
14012 // axis changes from vertical to horizontal or vice
14013 // versa)
14014 mColorAxis.data()->setLabel(labelTransfer);
14015 mColorAxis.data()->setScaleLogBase(
14016 logBaseTransfer); // scaleType is synchronized among axes in realtime
14017 // via signals (connected in QCPColorScale ctor), so
14018 // we only need to take care of log base here
14019 connect(mColorAxis.data(), SIGNAL(rangeChanged(QCPRange)), this,
14020 SLOT(setDataRange(QCPRange)));
14021 connect(mColorAxis.data(), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)),
14022 this, SLOT(setDataScaleType(QCPAxis::ScaleType)));
14023 mAxisRect.data()->setRangeDragAxes(
14024 QCPAxis::orientation(mType) == Qt::Horizontal ? mColorAxis.data() : 0,
14025 QCPAxis::orientation(mType) == Qt::Vertical ? mColorAxis.data() : 0);
14026 }
14027 }
14028
14029 /*!
14030 Sets the range spanned by the color gradient and that is shown by the axis in
14031 the color scale.
14032
14033 It is equivalent to calling QCPColorMap::setDataRange on any of the connected
14034 color maps. It is also equivalent to directly accessing the \ref axis and
14035 setting its range with \ref QCPAxis::setRange.
14036
14037 \see setDataScaleType, setGradient, rescaleDataRange
14038 */
14039 void QCPColorScale::setDataRange(const QCPRange &dataRange) {
14040 if (mDataRange.lower != dataRange.lower ||
14041 mDataRange.upper != dataRange.upper) {
14042 mDataRange = dataRange;
14043 if (mColorAxis) mColorAxis.data()->setRange(mDataRange);
14044 emit dataRangeChanged(mDataRange);
14045 }
14046 }
14047
14048 /*!
14049 Sets the scale type of the color scale, i.e. whether values are linearly
14050 associated with colors or logarithmically.
14051
14052 It is equivalent to calling QCPColorMap::setDataScaleType on any of the
14053 connected color maps. It is also equivalent to directly accessing the \ref
14054 axis and setting its scale type with \ref QCPAxis::setScaleType.
14055
14056 \see setDataRange, setGradient
14057 */
14058 void QCPColorScale::setDataScaleType(QCPAxis::ScaleType scaleType) {
14059 if (mDataScaleType != scaleType) {
14060 mDataScaleType = scaleType;
14061 if (mColorAxis) mColorAxis.data()->setScaleType(mDataScaleType);
14062 if (mDataScaleType == QCPAxis::stLogarithmic)
14063 setDataRange(mDataRange.sanitizedForLogScale());
14064 emit dataScaleTypeChanged(mDataScaleType);
14065 }
14066 }
14067
14068 /*!
14069 Sets the color gradient that will be used to represent data values.
14070
14071 It is equivalent to calling QCPColorMap::setGradient on any of the connected
14072 color maps.
14073
14074 \see setDataRange, setDataScaleType
14075 */
14076 void QCPColorScale::setGradient(const QCPColorGradient &gradient) {
14077 if (mGradient != gradient) {
14078 mGradient = gradient;
14079 if (mAxisRect) mAxisRect.data()->mGradientImageInvalidated = true;
14080 emit gradientChanged(mGradient);
14081 }
14082 }
14083
14084 /*!
14085 Sets the axis label of the color scale. This is equivalent to calling \ref
14086 QCPAxis::setLabel on the internal \ref axis.
14087 */
14088 void QCPColorScale::setLabel(const QString &str) {
14089 if (!mColorAxis) {
14090 qDebug() << Q_FUNC_INFO << "internal color axis undefined";
14091 return;
14092 }
14093
14094 mColorAxis.data()->setLabel(str);
14095 }
14096
14097 /*!
14098 Sets the width (or height, for horizontal color scales) the bar where the
14099 gradient is displayed will have.
14100 */
14101 void QCPColorScale::setBarWidth(int width) { mBarWidth = width; }
14102
14103 /*!
14104 Sets whether the user can drag the data range (\ref setDataRange).
14105
14106 Note that \ref QCP::iRangeDrag must be in the QCustomPlot's interactions (\ref
14107 QCustomPlot::setInteractions) to allow range dragging.
14108 */
14109 void QCPColorScale::setRangeDrag(bool enabled) {
14110 if (!mAxisRect) {
14111 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
14112 return;
14113 }
14114
14115 if (enabled)
14116 mAxisRect.data()->setRangeDrag(QCPAxis::orientation(mType));
14117 else
14118 mAxisRect.data()->setRangeDrag(0);
14119 }
14120
14121 /*!
14122 Sets whether the user can zoom the data range (\ref setDataRange) by scrolling
14123 the mouse wheel.
14124
14125 Note that \ref QCP::iRangeZoom must be in the QCustomPlot's interactions (\ref
14126 QCustomPlot::setInteractions) to allow range dragging.
14127 */
14128 void QCPColorScale::setRangeZoom(bool enabled) {
14129 if (!mAxisRect) {
14130 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
14131 return;
14132 }
14133
14134 if (enabled)
14135 mAxisRect.data()->setRangeZoom(QCPAxis::orientation(mType));
14136 else
14137 mAxisRect.data()->setRangeZoom(0);
14138 }
14139
14140 /*!
14141 Returns a list of all the color maps associated with this color scale.
14142 */
14143 QList<QCPColorMap *> QCPColorScale::colorMaps() const {
14144 QList<QCPColorMap *> result;
14145 for (int i = 0; i < mParentPlot->plottableCount(); ++i) {
14146 if (QCPColorMap *cm =
14147 qobject_cast<QCPColorMap *>(mParentPlot->plottable(i)))
14148 if (cm->colorScale() == this) result.append(cm);
14149 }
14150 return result;
14151 }
14152
14153 /*!
14154 Changes the data range such that all color maps associated with this color
14155 scale are fully mapped to the gradient in the data dimension.
14156
14157 \see setDataRange
14158 */
14159 void QCPColorScale::rescaleDataRange(bool onlyVisibleMaps) {
14160 QList<QCPColorMap *> maps = colorMaps();
14161 QCPRange newRange;
14162 bool haveRange = false;
14163 int sign = 0; // TODO: should change this to QCPAbstractPlottable::SignDomain
14164 // later (currently is protected, maybe move to QCP namespace)
14165 if (mDataScaleType == QCPAxis::stLogarithmic)
14166 sign = (mDataRange.upper < 0 ? -1 : 1);
14167 for (int i = 0; i < maps.size(); ++i) {
14168 if (!maps.at(i)->realVisibility() && onlyVisibleMaps) continue;
14169 QCPRange mapRange;
14170 if (maps.at(i)->colorScale() == this) {
14171 bool currentFoundRange = true;
14172 mapRange = maps.at(i)->data()->dataBounds();
14173 if (sign == 1) {
14174 if (mapRange.lower <= 0 && mapRange.upper > 0)
14175 mapRange.lower = mapRange.upper * 1e-3;
14176 else if (mapRange.lower <= 0 && mapRange.upper <= 0)
14177 currentFoundRange = false;
14178 } else if (sign == -1) {
14179 if (mapRange.upper >= 0 && mapRange.lower < 0)
14180 mapRange.upper = mapRange.lower * 1e-3;
14181 else if (mapRange.upper >= 0 && mapRange.lower >= 0)
14182 currentFoundRange = false;
14183 }
14184 if (currentFoundRange) {
14185 if (!haveRange)
14186 newRange = mapRange;
14187 else
14188 newRange.expand(mapRange);
14189 haveRange = true;
14190 }
14191 }
14192 }
14193 if (haveRange) {
14194 if (!QCPRange::validRange(
14195 newRange)) // likely due to range being zero (plottable has only
14196 // constant data in this dimension), shift current range
14197 // to at least center the data
14198 {
14199 double center =
14200 (newRange.lower + newRange.upper) *
14201 0.5; // upper and lower should be equal anyway, but just to make
14202 // sure, incase validRange returned false for other reason
14203 if (mDataScaleType == QCPAxis::stLinear) {
14204 newRange.lower = center - mDataRange.size() / 2.0;
14205 newRange.upper = center + mDataRange.size() / 2.0;
14206 } else // mScaleType == stLogarithmic
14207 {
14208 newRange.lower = center / qSqrt(mDataRange.upper / mDataRange.lower);
14209 newRange.upper = center * qSqrt(mDataRange.upper / mDataRange.lower);
14210 }
14211 }
14212 setDataRange(newRange);
14213 }
14214 }
14215
14216 /* inherits documentation from base class */
14217 void QCPColorScale::update(UpdatePhase phase) {
14218 QCPLayoutElement::update(phase);
14219 if (!mAxisRect) {
14220 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
14221 return;
14222 }
14223
14224 mAxisRect.data()->update(phase);
14225
14226 switch (phase) {
14227 case upMargins: {
14228 if (mType == QCPAxis::atBottom || mType == QCPAxis::atTop) {
14229 setMaximumSize(QWIDGETSIZE_MAX,
14230 mBarWidth + mAxisRect.data()->margins().top() +
14231 mAxisRect.data()->margins().bottom() +
14232 margins().top() + margins().bottom());
14233 setMinimumSize(0, mBarWidth + mAxisRect.data()->margins().top() +
14234 mAxisRect.data()->margins().bottom() +
14235 margins().top() + margins().bottom());
14236 } else {
14237 setMaximumSize(mBarWidth + mAxisRect.data()->margins().left() +
14238 mAxisRect.data()->margins().right() +
14239 margins().left() + margins().right(),
14240 QWIDGETSIZE_MAX);
14241 setMinimumSize(mBarWidth + mAxisRect.data()->margins().left() +
14242 mAxisRect.data()->margins().right() +
14243 margins().left() + margins().right(),
14244 0);
14245 }
14246 break;
14247 }
14248 case upLayout: {
14249 mAxisRect.data()->setOuterRect(rect());
14250 break;
14251 }
14252 default:
14253 break;
14254 }
14255 }
14256
14257 /* inherits documentation from base class */
14258 void QCPColorScale::applyDefaultAntialiasingHint(QCPPainter *painter) const {
14259 painter->setAntialiasing(false);
14260 }
14261
14262 /* inherits documentation from base class */
14263 void QCPColorScale::mousePressEvent(QMouseEvent *event) {
14264 if (!mAxisRect) {
14265 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
14266 return;
14267 }
14268 mAxisRect.data()->mousePressEvent(event);
14269 }
14270
14271 /* inherits documentation from base class */
14272 void QCPColorScale::mouseMoveEvent(QMouseEvent *event) {
14273 if (!mAxisRect) {
14274 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
14275 return;
14276 }
14277 mAxisRect.data()->mouseMoveEvent(event);
14278 }
14279
14280 /* inherits documentation from base class */
14281 void QCPColorScale::mouseReleaseEvent(QMouseEvent *event) {
14282 if (!mAxisRect) {
14283 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
14284 return;
14285 }
14286 mAxisRect.data()->mouseReleaseEvent(event);
14287 }
14288
14289 /* inherits documentation from base class */
14290 void QCPColorScale::wheelEvent(QWheelEvent *event) {
14291 if (!mAxisRect) {
14292 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
14293 return;
14294 }
14295 mAxisRect.data()->wheelEvent(event);
14296 }
14297
14298 ////////////////////////////////////////////////////////////////////////////////////////////////////
14299 //////////////////// QCPColorScaleAxisRectPrivate
14300 ////////////////////////////////////////////////////////////////////////////////////////////////////
14301
14302 /*! \class QCPColorScaleAxisRectPrivate
14303
14304 \internal
14305 \brief An axis rect subclass for use in a QCPColorScale
14306
14307 This is a private class and not part of the public QCustomPlot interface.
14308
14309 It provides the axis rect functionality for the QCPColorScale class.
14310 */
14311
14312 /*!
14313 Creates a new instance, as a child of \a parentColorScale.
14314 */
14315 QCPColorScaleAxisRectPrivate::QCPColorScaleAxisRectPrivate(
14316 QCPColorScale *parentColorScale)
14317 : QCPAxisRect(parentColorScale->parentPlot(), true),
14318 mParentColorScale(parentColorScale),
14319 mGradientImageInvalidated(true) {
14320 setParentLayerable(parentColorScale);
14321 setMinimumMargins(QMargins(0, 0, 0, 0));
14322 QList<QCPAxis::AxisType> allAxisTypes =
14323 QList<QCPAxis::AxisType>() << QCPAxis::atBottom << QCPAxis::atTop
14324 << QCPAxis::atLeft << QCPAxis::atRight;
14325 foreach (QCPAxis::AxisType type, allAxisTypes) {
14326 axis(type)->setVisible(true);
14327 axis(type)->grid()->setVisible(false);
14328 axis(type)->setPadding(0);
14329 connect(axis(type), SIGNAL(selectionChanged(QCPAxis::SelectableParts)),
14330 this, SLOT(axisSelectionChanged(QCPAxis::SelectableParts)));
14331 connect(axis(type), SIGNAL(selectableChanged(QCPAxis::SelectableParts)),
14332 this, SLOT(axisSelectableChanged(QCPAxis::SelectableParts)));
14333 }
14334
14335 connect(axis(QCPAxis::atLeft), SIGNAL(rangeChanged(QCPRange)),
14336 axis(QCPAxis::atRight), SLOT(setRange(QCPRange)));
14337 connect(axis(QCPAxis::atRight), SIGNAL(rangeChanged(QCPRange)),
14338 axis(QCPAxis::atLeft), SLOT(setRange(QCPRange)));
14339 connect(axis(QCPAxis::atBottom), SIGNAL(rangeChanged(QCPRange)),
14340 axis(QCPAxis::atTop), SLOT(setRange(QCPRange)));
14341 connect(axis(QCPAxis::atTop), SIGNAL(rangeChanged(QCPRange)),
14342 axis(QCPAxis::atBottom), SLOT(setRange(QCPRange)));
14343 connect(axis(QCPAxis::atLeft), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)),
14344 axis(QCPAxis::atRight), SLOT(setScaleType(QCPAxis::ScaleType)));
14345 connect(axis(QCPAxis::atRight), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)),
14346 axis(QCPAxis::atLeft), SLOT(setScaleType(QCPAxis::ScaleType)));
14347 connect(axis(QCPAxis::atBottom), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)),
14348 axis(QCPAxis::atTop), SLOT(setScaleType(QCPAxis::ScaleType)));
14349 connect(axis(QCPAxis::atTop), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)),
14350 axis(QCPAxis::atBottom), SLOT(setScaleType(QCPAxis::ScaleType)));
14351
14352 // make layer transfers of color scale transfer to axis rect and axes
14353 // the axes must be set after axis rect, such that they appear above color
14354 // gradient drawn by axis rect:
14355 connect(parentColorScale, SIGNAL(layerChanged(QCPLayer *)), this,
14356 SLOT(setLayer(QCPLayer *)));
14357 foreach (QCPAxis::AxisType type, allAxisTypes)
14358 connect(parentColorScale, SIGNAL(layerChanged(QCPLayer *)), axis(type),
14359 SLOT(setLayer(QCPLayer *)));
14360 }
14361
14362 /*! \internal
14363 Updates the color gradient image if necessary, by calling \ref
14364 updateGradientImage, then draws it. Then the axes are drawn by calling the
14365 \ref QCPAxisRect::draw base class implementation.
14366 */
14367 void QCPColorScaleAxisRectPrivate::draw(QCPPainter *painter) {
14368 if (mGradientImageInvalidated) updateGradientImage();
14369
14370 bool mirrorHorz = false;
14371 bool mirrorVert = false;
14372 if (mParentColorScale->mColorAxis) {
14373 mirrorHorz = mParentColorScale->mColorAxis.data()->rangeReversed() &&
14374 (mParentColorScale->type() == QCPAxis::atBottom ||
14375 mParentColorScale->type() == QCPAxis::atTop);
14376 mirrorVert = mParentColorScale->mColorAxis.data()->rangeReversed() &&
14377 (mParentColorScale->type() == QCPAxis::atLeft ||
14378 mParentColorScale->type() == QCPAxis::atRight);
14379 }
14380
14381 painter->drawImage(rect().adjusted(0, -1, 0, -1),
14382 mGradientImage.mirrored(mirrorHorz, mirrorVert));
14383 QCPAxisRect::draw(painter);
14384 }
14385
14386 /*! \internal
14387
14388 Uses the current gradient of the parent \ref QCPColorScale (specified in the
14389 constructor) to generate a gradient image. This gradient image will be used in
14390 the \ref draw method.
14391 */
14392 void QCPColorScaleAxisRectPrivate::updateGradientImage() {
14393 if (rect().isEmpty()) return;
14394
14395 int n = mParentColorScale->mGradient.levelCount();
14396 int w, h;
14397 QVector<double> data(n);
14398 for (int i = 0; i < n; ++i) data[i] = i;
14399 if (mParentColorScale->mType == QCPAxis::atBottom ||
14400 mParentColorScale->mType == QCPAxis::atTop) {
14401 w = n;
14402 h = rect().height();
14403 mGradientImage = QImage(w, h, QImage::Format_RGB32);
14404 QVector<QRgb *> pixels;
14405 for (int y = 0; y < h; ++y)
14406 pixels.append(reinterpret_cast<QRgb *>(mGradientImage.scanLine(y)));
14407 mParentColorScale->mGradient.colorize(data.constData(), QCPRange(0, n - 1),
14408 pixels.first(), n);
14409 for (int y = 1; y < h; ++y)
14410 memcpy(pixels.at(y), pixels.first(), n * sizeof(QRgb));
14411 } else {
14412 w = rect().width();
14413 h = n;
14414 mGradientImage = QImage(w, h, QImage::Format_RGB32);
14415 for (int y = 0; y < h; ++y) {
14416 QRgb *pixels = reinterpret_cast<QRgb *>(mGradientImage.scanLine(y));
14417 const QRgb lineColor = mParentColorScale->mGradient.color(
14418 data[h - 1 - y], QCPRange(0, n - 1));
14419 for (int x = 0; x < w; ++x) pixels[x] = lineColor;
14420 }
14421 }
14422 mGradientImageInvalidated = false;
14423 }
14424
14425 /*! \internal
14426
14427 This slot is connected to the selectionChanged signals of the four axes in the
14428 constructor. It synchronizes the selection state of the axes.
14429 */
14430 void QCPColorScaleAxisRectPrivate::axisSelectionChanged(
14431 QCPAxis::SelectableParts selectedParts) {
14432 // axis bases of four axes shall always (de-)selected synchronously:
14433 QList<QCPAxis::AxisType> allAxisTypes =
14434 QList<QCPAxis::AxisType>() << QCPAxis::atBottom << QCPAxis::atTop
14435 << QCPAxis::atLeft << QCPAxis::atRight;
14436 foreach (QCPAxis::AxisType type, allAxisTypes) {
14437 if (QCPAxis *senderAxis = qobject_cast<QCPAxis *>(sender()))
14438 if (senderAxis->axisType() == type) continue;
14439
14440 if (axis(type)->selectableParts().testFlag(QCPAxis::spAxis)) {
14441 if (selectedParts.testFlag(QCPAxis::spAxis))
14442 axis(type)->setSelectedParts(axis(type)->selectedParts() |
14443 QCPAxis::spAxis);
14444 else
14445 axis(type)->setSelectedParts(axis(type)->selectedParts() &
14446 ~QCPAxis::spAxis);
14447 }
14448 }
14449 }
14450
14451 /*! \internal
14452
14453 This slot is connected to the selectableChanged signals of the four axes in
14454 the constructor. It synchronizes the selectability of the axes.
14455 */
14456 void QCPColorScaleAxisRectPrivate::axisSelectableChanged(
14457 QCPAxis::SelectableParts selectableParts) {
14458 // synchronize axis base selectability:
14459 QList<QCPAxis::AxisType> allAxisTypes =
14460 QList<QCPAxis::AxisType>() << QCPAxis::atBottom << QCPAxis::atTop
14461 << QCPAxis::atLeft << QCPAxis::atRight;
14462 foreach (QCPAxis::AxisType type, allAxisTypes) {
14463 if (QCPAxis *senderAxis = qobject_cast<QCPAxis *>(sender()))
14464 if (senderAxis->axisType() == type) continue;
14465
14466 if (axis(type)->selectableParts().testFlag(QCPAxis::spAxis)) {
14467 if (selectableParts.testFlag(QCPAxis::spAxis))
14468 axis(type)->setSelectableParts(axis(type)->selectableParts() |
14469 QCPAxis::spAxis);
14470 else
14471 axis(type)->setSelectableParts(axis(type)->selectableParts() &
14472 ~QCPAxis::spAxis);
14473 }
14474 }
14475 }
14476
14477 ////////////////////////////////////////////////////////////////////////////////////////////////////
14478 //////////////////// QCPData
14479 ////////////////////////////////////////////////////////////////////////////////////////////////////
14480
14481 /*! \class QCPData
14482 \brief Holds the data of one single data point for QCPGraph.
14483
14484 The container for storing multiple data points is \ref QCPDataMap.
14485
14486 The stored data is:
14487 \li \a key: coordinate on the key axis of this data point
14488 \li \a value: coordinate on the value axis of this data point
14489 \li \a keyErrorMinus: negative error in the key dimension (for error bars)
14490 \li \a keyErrorPlus: positive error in the key dimension (for error bars)
14491 \li \a valueErrorMinus: negative error in the value dimension (for error bars)
14492 \li \a valueErrorPlus: positive error in the value dimension (for error bars)
14493
14494 \see QCPDataMap
14495 */
14496
14497 /*!
14498 Constructs a data point with key, value and all errors set to zero.
14499 */
14500 QCPData::QCPData()
14501 : key(0),
14502 value(0),
14503 keyErrorPlus(0),
14504 keyErrorMinus(0),
14505 valueErrorPlus(0),
14506 valueErrorMinus(0) {}
14507
14508 /*!
14509 Constructs a data point with the specified \a key and \a value. All errors are
14510 set to zero.
14511 */
14512 QCPData::QCPData(double key, double value)
14513 : key(key),
14514 value(value),
14515 keyErrorPlus(0),
14516 keyErrorMinus(0),
14517 valueErrorPlus(0),
14518 valueErrorMinus(0) {}
14519
14520 ////////////////////////////////////////////////////////////////////////////////////////////////////
14521 //////////////////// QCPGraph
14522 ////////////////////////////////////////////////////////////////////////////////////////////////////
14523
14524 /*! \class QCPGraph
14525 \brief A plottable representing a graph in a plot.
14526
14527 \image html QCPGraph.png
14528
14529 Usually you create new graphs by calling QCustomPlot::addGraph. The resulting
14530 instance can be accessed via QCustomPlot::graph.
14531
14532 To plot data, assign it with the \ref setData or \ref addData functions.
14533 Alternatively, you can also access and modify the graph's data via the \ref
14534 data method, which returns a pointer to the internal \ref QCPDataMap.
14535
14536 Graphs are used to display single-valued data. Single-valued means that there
14537 should only be one data point per unique key coordinate. In other words, the
14538 graph can't have \a loops. If you do want to plot non-single-valued curves,
14539 rather use the QCPCurve plottable.
14540
14541 Gaps in the graph line can be created by adding data points with NaN as value
14542 (<tt>qQNaN()</tt> or <tt>std::numeric_limits<double>::quiet_NaN()</tt>) in
14543 between the two data points that shall be separated.
14544
14545 \section appearance Changing the appearance
14546
14547 The appearance of the graph is mainly determined by the line style, scatter
14548 style, brush and pen of the graph (\ref setLineStyle, \ref setScatterStyle,
14549 \ref setBrush, \ref setPen).
14550
14551 \subsection filling Filling under or between graphs
14552
14553 QCPGraph knows two types of fills: Normal graph fills towards the
14554 zero-value-line parallel to the key axis of the graph, and fills between two
14555 graphs, called channel fills. To enable a fill, just set a brush with \ref
14556 setBrush which is neither Qt::NoBrush nor fully transparent.
14557
14558 By default, a normal fill towards the zero-value-line will be drawn. To set up
14559 a channel fill between this graph and another one, call \ref
14560 setChannelFillGraph with the other graph as parameter.
14561
14562 \see QCustomPlot::addGraph, QCustomPlot::graph
14563 */
14564
14565 /* start of documentation of inline functions */
14566
14567 /*! \fn QCPDataMap *QCPGraph::data() const
14568
14569 Returns a pointer to the internal data storage of type \ref QCPDataMap. You
14570 may use it to directly manipulate the data, which may be more convenient and
14571 faster than using the regular \ref setData or \ref addData methods, in certain
14572 situations.
14573 */
14574
14575 /* end of documentation of inline functions */
14576
14577 /*!
14578 Constructs a graph which uses \a keyAxis as its key axis ("x") and \a
14579 valueAxis as its value axis ("y"). \a keyAxis and \a valueAxis must reside in
14580 the same QCustomPlot instance and not have the same orientation. If either of
14581 these restrictions is violated, a corresponding message is printed to the
14582 debug output (qDebug), the construction is not aborted, though.
14583
14584 The constructed QCPGraph can be added to the plot with
14585 QCustomPlot::addPlottable, QCustomPlot then takes ownership of the graph.
14586
14587 To directly create a graph inside a plot, you can also use the simpler
14588 QCustomPlot::addGraph function.
14589 */
14590 QCPGraph::QCPGraph(QCPAxis *keyAxis, QCPAxis *valueAxis)
14591 : QCPAbstractPlottable(keyAxis, valueAxis) {
14592 mData = new QCPDataMap;
14593
14594 setPen(QPen(Qt::blue, 0));
14595 setErrorPen(QPen(Qt::black));
14596 setBrush(Qt::NoBrush);
14597 setSelectedPen(QPen(QColor(80, 80, 255), 2.5));
14598 setSelectedBrush(Qt::NoBrush);
14599
14600 setLineStyle(lsLine);
14601 setErrorType(etNone);
14602 setErrorBarSize(6);
14603 setErrorBarSkipSymbol(true);
14604 setChannelFillGraph(0);
14605 setAdaptiveSampling(true);
14606 }
14607
14608 QCPGraph::~QCPGraph() { delete mData; }
14609
14610 /*!
14611 Replaces the current data with the provided \a data.
14612
14613 If \a copy is set to true, data points in \a data will only be copied. if
14614 false, the graph takes ownership of the passed data and replaces the internal
14615 data pointer with it. This is significantly faster than copying for large
14616 datasets.
14617
14618 Alternatively, you can also access and modify the graph's data via the \ref
14619 data method, which returns a pointer to the internal \ref QCPDataMap.
14620 */
14621 void QCPGraph::setData(QCPDataMap *data, bool copy) {
14622 if (mData == data) {
14623 qDebug() << Q_FUNC_INFO
14624 << "The data pointer is already in (and owned by) this plottable"
14625 << reinterpret_cast<quintptr>(data);
14626 return;
14627 }
14628 if (copy) {
14629 *mData = *data;
14630 } else {
14631 delete mData;
14632 mData = data;
14633 }
14634 }
14635
14636 /*! \overload
14637
14638 Replaces the current data with the provided points in \a key and \a value
14639 pairs. The provided vectors should have equal length. Else, the number of
14640 added points will be the size of the smallest vector.
14641 */
14642 void QCPGraph::setData(const QVector<double> &key,
14643 const QVector<double> &value) {
14644 mData->clear();
14645 int n = key.size();
14646 n = qMin(n, value.size());
14647 QCPData newData;
14648 for (int i = 0; i < n; ++i) {
14649 newData.key = key[i];
14650 newData.value = value[i];
14651 mData->insertMulti(newData.key, newData);
14652 }
14653 }
14654
14655 /*!
14656 Replaces the current data with the provided points in \a key and \a value
14657 pairs. Additionally the symmetrical value error of the data points are set to
14658 the values in \a valueError. For error bars to show appropriately, see \ref
14659 setErrorType. The provided vectors should have equal length. Else, the number
14660 of added points will be the size of the smallest vector.
14661
14662 For asymmetrical errors (plus different from minus), see the overloaded
14663 version of this function.
14664 */
14665 void QCPGraph::setDataValueError(const QVector<double> &key,
14666 const QVector<double> &value,
14667 const QVector<double> &valueError) {
14668 mData->clear();
14669 int n = key.size();
14670 n = qMin(n, value.size());
14671 n = qMin(n, valueError.size());
14672 QCPData newData;
14673 for (int i = 0; i < n; ++i) {
14674 newData.key = key[i];
14675 newData.value = value[i];
14676 newData.valueErrorMinus = valueError[i];
14677 newData.valueErrorPlus = valueError[i];
14678 mData->insertMulti(key[i], newData);
14679 }
14680 }
14681
14682 /*!
14683 \overload
14684 Replaces the current data with the provided points in \a key and \a value
14685 pairs. Additionally the negative value error of the data points are set to the
14686 values in \a valueErrorMinus, the positive value error to \a valueErrorPlus.
14687 For error bars to show appropriately, see \ref setErrorType.
14688 The provided vectors should have equal length. Else, the number of added
14689 points will be the size of the smallest vector.
14690 */
14691 void QCPGraph::setDataValueError(const QVector<double> &key,
14692 const QVector<double> &value,
14693 const QVector<double> &valueErrorMinus,
14694 const QVector<double> &valueErrorPlus) {
14695 mData->clear();
14696 int n = key.size();
14697 n = qMin(n, value.size());
14698 n = qMin(n, valueErrorMinus.size());
14699 n = qMin(n, valueErrorPlus.size());
14700 QCPData newData;
14701 for (int i = 0; i < n; ++i) {
14702 newData.key = key[i];
14703 newData.value = value[i];
14704 newData.valueErrorMinus = valueErrorMinus[i];
14705 newData.valueErrorPlus = valueErrorPlus[i];
14706 mData->insertMulti(key[i], newData);
14707 }
14708 }
14709
14710 /*!
14711 Replaces the current data with the provided points in \a key and \a value
14712 pairs. Additionally the symmetrical key error of the data points are set to
14713 the values in \a keyError. For error bars to show appropriately, see \ref
14714 setErrorType. The provided vectors should have equal length. Else, the number
14715 of added points will be the size of the smallest vector.
14716
14717 For asymmetrical errors (plus different from minus), see the overloaded
14718 version of this function.
14719 */
14720 void QCPGraph::setDataKeyError(const QVector<double> &key,
14721 const QVector<double> &value,
14722 const QVector<double> &keyError) {
14723 mData->clear();
14724 int n = key.size();
14725 n = qMin(n, value.size());
14726 n = qMin(n, keyError.size());
14727 QCPData newData;
14728 for (int i = 0; i < n; ++i) {
14729 newData.key = key[i];
14730 newData.value = value[i];
14731 newData.keyErrorMinus = keyError[i];
14732 newData.keyErrorPlus = keyError[i];
14733 mData->insertMulti(key[i], newData);
14734 }
14735 }
14736
14737 /*!
14738 \overload
14739 Replaces the current data with the provided points in \a key and \a value
14740 pairs. Additionally the negative key error of the data points are set to the
14741 values in \a keyErrorMinus, the positive key error to \a keyErrorPlus. For
14742 error bars to show appropriately, see \ref setErrorType. The provided vectors
14743 should have equal length. Else, the number of added points will be the size of
14744 the smallest vector.
14745 */
14746 void QCPGraph::setDataKeyError(const QVector<double> &key,
14747 const QVector<double> &value,
14748 const QVector<double> &keyErrorMinus,
14749 const QVector<double> &keyErrorPlus) {
14750 mData->clear();
14751 int n = key.size();
14752 n = qMin(n, value.size());
14753 n = qMin(n, keyErrorMinus.size());
14754 n = qMin(n, keyErrorPlus.size());
14755 QCPData newData;
14756 for (int i = 0; i < n; ++i) {
14757 newData.key = key[i];
14758 newData.value = value[i];
14759 newData.keyErrorMinus = keyErrorMinus[i];
14760 newData.keyErrorPlus = keyErrorPlus[i];
14761 mData->insertMulti(key[i], newData);
14762 }
14763 }
14764
14765 /*!
14766 Replaces the current data with the provided points in \a key and \a value
14767 pairs. Additionally the symmetrical key and value errors of the data points
14768 are set to the values in \a keyError and \a valueError. For error bars to show
14769 appropriately, see \ref setErrorType. The provided vectors should have equal
14770 length. Else, the number of added points will be the size of the smallest
14771 vector.
14772
14773 For asymmetrical errors (plus different from minus), see the overloaded
14774 version of this function.
14775 */
14776 void QCPGraph::setDataBothError(const QVector<double> &key,
14777 const QVector<double> &value,
14778 const QVector<double> &keyError,
14779 const QVector<double> &valueError) {
14780 mData->clear();
14781 int n = key.size();
14782 n = qMin(n, value.size());
14783 n = qMin(n, valueError.size());
14784 n = qMin(n, keyError.size());
14785 QCPData newData;
14786 for (int i = 0; i < n; ++i) {
14787 newData.key = key[i];
14788 newData.value = value[i];
14789 newData.keyErrorMinus = keyError[i];
14790 newData.keyErrorPlus = keyError[i];
14791 newData.valueErrorMinus = valueError[i];
14792 newData.valueErrorPlus = valueError[i];
14793 mData->insertMulti(key[i], newData);
14794 }
14795 }
14796
14797 /*!
14798 \overload
14799 Replaces the current data with the provided points in \a key and \a value
14800 pairs. Additionally the negative key and value errors of the data points are
14801 set to the values in \a keyErrorMinus and \a valueErrorMinus. The positive key
14802 and value errors are set to the values in \a keyErrorPlus \a valueErrorPlus.
14803 For error bars to show appropriately, see \ref setErrorType.
14804 The provided vectors should have equal length. Else, the number of added
14805 points will be the size of the smallest vector.
14806 */
14807 void QCPGraph::setDataBothError(const QVector<double> &key,
14808 const QVector<double> &value,
14809 const QVector<double> &keyErrorMinus,
14810 const QVector<double> &keyErrorPlus,
14811 const QVector<double> &valueErrorMinus,
14812 const QVector<double> &valueErrorPlus) {
14813 mData->clear();
14814 int n = key.size();
14815 n = qMin(n, value.size());
14816 n = qMin(n, valueErrorMinus.size());
14817 n = qMin(n, valueErrorPlus.size());
14818 n = qMin(n, keyErrorMinus.size());
14819 n = qMin(n, keyErrorPlus.size());
14820 QCPData newData;
14821 for (int i = 0; i < n; ++i) {
14822 newData.key = key[i];
14823 newData.value = value[i];
14824 newData.keyErrorMinus = keyErrorMinus[i];
14825 newData.keyErrorPlus = keyErrorPlus[i];
14826 newData.valueErrorMinus = valueErrorMinus[i];
14827 newData.valueErrorPlus = valueErrorPlus[i];
14828 mData->insertMulti(key[i], newData);
14829 }
14830 }
14831
14832 /*!
14833 Sets how the single data points are connected in the plot. For scatter-only
14834 plots, set \a ls to \ref lsNone and \ref setScatterStyle to the desired
14835 scatter style.
14836
14837 \see setScatterStyle
14838 */
14839 void QCPGraph::setLineStyle(LineStyle ls) { mLineStyle = ls; }
14840
14841 /*!
14842 Sets the visual appearance of single data points in the plot. If set to \ref
14843 QCPScatterStyle::ssNone, no scatter points are drawn (e.g. for line-only-plots
14844 with appropriate line style).
14845
14846 \see QCPScatterStyle, setLineStyle
14847 */
14848 void QCPGraph::setScatterStyle(const QCPScatterStyle &style) {
14849 mScatterStyle = style;
14850 }
14851
14852 /*!
14853 Sets which kind of error bars (Key Error, Value Error or both) should be drawn
14854 on each data point. If you set \a errorType to something other than \ref
14855 etNone, make sure to actually pass error data via the specific setData
14856 functions along with the data points (e.g. \ref setDataValueError, \ref
14857 setDataKeyError, \ref setDataBothError).
14858
14859 \see ErrorType
14860 */
14861 void QCPGraph::setErrorType(ErrorType errorType) { mErrorType = errorType; }
14862
14863 /*!
14864 Sets the pen with which the error bars will be drawn.
14865 \see setErrorBarSize, setErrorType
14866 */
14867 void QCPGraph::setErrorPen(const QPen &pen) { mErrorPen = pen; }
14868
14869 /*!
14870 Sets the width of the handles at both ends of an error bar in pixels.
14871 */
14872 void QCPGraph::setErrorBarSize(double size) { mErrorBarSize = size; }
14873
14874 /*!
14875 If \a enabled is set to true, the error bar will not be drawn as a solid line
14876 under the scatter symbol but leave some free space around the symbol.
14877
14878 This feature uses the current scatter size (\ref QCPScatterStyle::setSize) to
14879 determine the size of the area to leave blank. So when drawing Pixmaps as
14880 scatter points (\ref QCPScatterStyle::ssPixmap), the scatter size must be set
14881 manually to a value corresponding to the size of the Pixmap, if the error bars
14882 should leave gaps to its boundaries.
14883
14884 \ref setErrorType, setErrorBarSize, setScatterStyle
14885 */
14886 void QCPGraph::setErrorBarSkipSymbol(bool enabled) {
14887 mErrorBarSkipSymbol = enabled;
14888 }
14889
14890 /*!
14891 Sets the target graph for filling the area between this graph and \a
14892 targetGraph with the current brush (\ref setBrush).
14893
14894 When \a targetGraph is set to 0, a normal graph fill to the zero-value-line
14895 will be shown. To disable any filling, set the brush to Qt::NoBrush.
14896
14897 \see setBrush
14898 */
14899 void QCPGraph::setChannelFillGraph(QCPGraph *targetGraph) {
14900 // prevent setting channel target to this graph itself:
14901 if (targetGraph == this) {
14902 qDebug() << Q_FUNC_INFO << "targetGraph is this graph itself";
14903 mChannelFillGraph = 0;
14904 return;
14905 }
14906 // prevent setting channel target to a graph not in the plot:
14907 if (targetGraph && targetGraph->mParentPlot != mParentPlot) {
14908 qDebug() << Q_FUNC_INFO << "targetGraph not in same plot";
14909 mChannelFillGraph = 0;
14910 return;
14911 }
14912
14913 mChannelFillGraph = targetGraph;
14914 }
14915
14916 /*!
14917 Sets whether adaptive sampling shall be used when plotting this graph.
14918 QCustomPlot's adaptive sampling technique can drastically improve the replot
14919 performance for graphs with a larger number of points (e.g. above 10,000),
14920 without notably changing the appearance of the graph.
14921
14922 By default, adaptive sampling is enabled. Even if enabled, QCustomPlot decides
14923 whether adaptive sampling shall actually be used on a per-graph basis. So
14924 leaving adaptive sampling enabled has no disadvantage in almost all cases.
14925
14926 \image html adaptive-sampling-line.png "A line plot of 500,000 points without
14927 and with adaptive sampling"
14928
14929 As can be seen, line plots experience no visual degradation from adaptive
14930 sampling. Outliers are reproduced reliably, as well as the overall shape of
14931 the data set. The replot time reduces dramatically though. This allows
14932 QCustomPlot to display large amounts of data in realtime.
14933
14934 \image html adaptive-sampling-scatter.png "A scatter plot of 100,000 points
14935 without and with adaptive sampling"
14936
14937 Care must be taken when using high-density scatter plots in combination with
14938 adaptive sampling. The adaptive sampling algorithm treats scatter plots more
14939 carefully than line plots which still gives a significant reduction of replot
14940 times, but not quite as much as for line plots. This is because scatter plots
14941 inherently need more data points to be preserved in order to still resemble
14942 the original, non-adaptive-sampling plot. As shown above, the results still
14943 aren't quite identical, as banding occurs for the outer data points. This is
14944 in fact intentional, such that the boundaries of the data cloud stay visible
14945 to the viewer. How strong the banding appears, depends on the point density,
14946 i.e. the number of points in the plot.
14947
14948 For some situations with scatter plots it might thus be desirable to manually
14949 turn adaptive sampling off. For example, when saving the plot to disk. This
14950 can be achieved by setting \a enabled to false before issuing a command like
14951 \ref QCustomPlot::savePng, and setting \a enabled back to true afterwards.
14952 */
14953 void QCPGraph::setAdaptiveSampling(bool enabled) {
14954 mAdaptiveSampling = enabled;
14955 }
14956
14957 /*!
14958 Adds the provided data points in \a dataMap to the current data.
14959
14960 Alternatively, you can also access and modify the graph's data via the \ref
14961 data method, which returns a pointer to the internal \ref QCPDataMap.
14962
14963 \see removeData
14964 */
14965 void QCPGraph::addData(const QCPDataMap &dataMap) { mData->unite(dataMap); }
14966
14967 /*! \overload
14968 Adds the provided single data point in \a data to the current data.
14969
14970 Alternatively, you can also access and modify the graph's data via the \ref
14971 data method, which returns a pointer to the internal \ref QCPDataMap.
14972
14973 \see removeData
14974 */
14975 void QCPGraph::addData(const QCPData &data) {
14976 mData->insertMulti(data.key, data);
14977 }
14978
14979 /*! \overload
14980 Adds the provided single data point as \a key and \a value pair to the current
14981 data.
14982
14983 Alternatively, you can also access and modify the graph's data via the \ref
14984 data method, which returns a pointer to the internal \ref QCPDataMap.
14985
14986 \see removeData
14987 */
14988 void QCPGraph::addData(double key, double value) {
14989 QCPData newData;
14990 newData.key = key;
14991 newData.value = value;
14992 mData->insertMulti(newData.key, newData);
14993 }
14994
14995 /*! \overload
14996 Adds the provided data points as \a key and \a value pairs to the current
14997 data.
14998
14999 Alternatively, you can also access and modify the graph's data via the \ref
15000 data method, which returns a pointer to the internal \ref QCPDataMap.
15001
15002 \see removeData
15003 */
15004 void QCPGraph::addData(const QVector<double> &keys,
15005 const QVector<double> &values) {
15006 int n = qMin(keys.size(), values.size());
15007 QCPData newData;
15008 for (int i = 0; i < n; ++i) {
15009 newData.key = keys[i];
15010 newData.value = values[i];
15011 mData->insertMulti(newData.key, newData);
15012 }
15013 }
15014
15015 /*!
15016 Removes all data points with keys smaller than \a key.
15017 \see addData, clearData
15018 */
15019 void QCPGraph::removeDataBefore(double key) {
15020 QCPDataMap::iterator it = mData->begin();
15021 while (it != mData->end() && it.key() < key) it = mData->erase(it);
15022 }
15023
15024 /*!
15025 Removes all data points with keys greater than \a key.
15026 \see addData, clearData
15027 */
15028 void QCPGraph::removeDataAfter(double key) {
15029 if (mData->isEmpty()) return;
15030 QCPDataMap::iterator it = mData->upperBound(key);
15031 while (it != mData->end()) it = mData->erase(it);
15032 }
15033
15034 /*!
15035 Removes all data points with keys between \a fromKey and \a toKey.
15036 if \a fromKey is greater or equal to \a toKey, the function does nothing. To
15037 remove a single data point with known key, use \ref removeData(double key).
15038
15039 \see addData, clearData
15040 */
15041 void QCPGraph::removeData(double fromKey, double toKey) {
15042 if (fromKey >= toKey || mData->isEmpty()) return;
15043 QCPDataMap::iterator it = mData->upperBound(fromKey);
15044 QCPDataMap::iterator itEnd = mData->upperBound(toKey);
15045 while (it != itEnd) it = mData->erase(it);
15046 }
15047
15048 /*! \overload
15049
15050 Removes a single data point at \a key. If the position is not known with
15051 absolute precision, consider using \ref removeData(double fromKey, double
15052 toKey) with a small fuzziness interval around the suspected position, depeding
15053 on the precision with which the key is known.
15054
15055 \see addData, clearData
15056 */
15057 void QCPGraph::removeData(double key) { mData->remove(key); }
15058
15059 /*!
15060 Removes all data points.
15061 \see removeData, removeDataAfter, removeDataBefore
15062 */
15063 void QCPGraph::clearData() { mData->clear(); }
15064
15065 /* inherits documentation from base class */
15066 double QCPGraph::selectTest(const QPointF &pos, bool onlySelectable,
15067 QVariant *details) const {
15068 Q_UNUSED(details)
15069 if ((onlySelectable && !mSelectable) || mData->isEmpty()) return -1;
15070 if (!mKeyAxis || !mValueAxis) {
15071 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
15072 return -1;
15073 }
15074
15075 if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
15076 return pointDistance(pos);
15077 else
15078 return -1;
15079 }
15080
15081 /*! \overload
15082
15083 Allows to define whether error bars are taken into consideration when
15084 determining the new axis range.
15085
15086 \see rescaleKeyAxis, rescaleValueAxis, QCPAbstractPlottable::rescaleAxes,
15087 QCustomPlot::rescaleAxes
15088 */
15089 void QCPGraph::rescaleAxes(bool onlyEnlarge, bool includeErrorBars) const {
15090 rescaleKeyAxis(onlyEnlarge, includeErrorBars);
15091 rescaleValueAxis(onlyEnlarge, includeErrorBars);
15092 }
15093
15094 /*! \overload
15095
15096 Allows to define whether error bars (of kind \ref QCPGraph::etKey) are taken
15097 into consideration when determining the new axis range.
15098
15099 \see rescaleAxes, QCPAbstractPlottable::rescaleKeyAxis
15100 */
15101 void QCPGraph::rescaleKeyAxis(bool onlyEnlarge, bool includeErrorBars) const {
15102 // this code is a copy of QCPAbstractPlottable::rescaleKeyAxis with the only
15103 // change that getKeyRange is passed the includeErrorBars value.
15104 if (mData->isEmpty()) return;
15105
15106 QCPAxis *keyAxis = mKeyAxis.data();
15107 if (!keyAxis) {
15108 qDebug() << Q_FUNC_INFO << "invalid key axis";
15109 return;
15110 }
15111
15112 SignDomain signDomain = sdBoth;
15113 if (keyAxis->scaleType() == QCPAxis::stLogarithmic)
15114 signDomain = (keyAxis->range().upper < 0 ? sdNegative : sdPositive);
15115
15116 bool foundRange;
15117 QCPRange newRange = getKeyRange(foundRange, signDomain, includeErrorBars);
15118
15119 if (foundRange) {
15120 if (onlyEnlarge) {
15121 if (keyAxis->range().lower < newRange.lower)
15122 newRange.lower = keyAxis->range().lower;
15123 if (keyAxis->range().upper > newRange.upper)
15124 newRange.upper = keyAxis->range().upper;
15125 }
15126 keyAxis->setRange(newRange);
15127 }
15128 }
15129
15130 /*! \overload
15131
15132 Allows to define whether error bars (of kind \ref QCPGraph::etValue) are taken
15133 into consideration when determining the new axis range.
15134
15135 \see rescaleAxes, QCPAbstractPlottable::rescaleValueAxis
15136 */
15137 void QCPGraph::rescaleValueAxis(bool onlyEnlarge, bool includeErrorBars) const {
15138 // this code is a copy of QCPAbstractPlottable::rescaleValueAxis with the only
15139 // change is that getValueRange is passed the includeErrorBars value.
15140 if (mData->isEmpty()) return;
15141
15142 QCPAxis *valueAxis = mValueAxis.data();
15143 if (!valueAxis) {
15144 qDebug() << Q_FUNC_INFO << "invalid value axis";
15145 return;
15146 }
15147
15148 SignDomain signDomain = sdBoth;
15149 if (valueAxis->scaleType() == QCPAxis::stLogarithmic)
15150 signDomain = (valueAxis->range().upper < 0 ? sdNegative : sdPositive);
15151
15152 bool foundRange;
15153 QCPRange newRange = getValueRange(foundRange, signDomain, includeErrorBars);
15154
15155 if (foundRange) {
15156 if (onlyEnlarge) {
15157 if (valueAxis->range().lower < newRange.lower)
15158 newRange.lower = valueAxis->range().lower;
15159 if (valueAxis->range().upper > newRange.upper)
15160 newRange.upper = valueAxis->range().upper;
15161 }
15162 valueAxis->setRange(newRange);
15163 }
15164 }
15165
15166 /* inherits documentation from base class */
15167 void QCPGraph::draw(QCPPainter *painter) {
15168 if (!mKeyAxis || !mValueAxis) {
15169 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
15170 return;
15171 }
15172 if (mKeyAxis.data()->range().size() <= 0 || mData->isEmpty()) return;
15173 if (mLineStyle == lsNone && mScatterStyle.isNone()) return;
15174
15175 // allocate line and (if necessary) point vectors:
15176 QVector<QPointF> *lineData = new QVector<QPointF>;
15177 QVector<QCPData> *scatterData = 0;
15178 if (!mScatterStyle.isNone()) scatterData = new QVector<QCPData>;
15179
15180 // fill vectors with data appropriate to plot style:
15181 getPlotData(lineData, scatterData);
15182
15183 // check data validity if flag set:
15184 #ifdef QCUSTOMPLOT_CHECK_DATA
15185 QCPDataMap::const_iterator it;
15186 for (it = mData->constBegin(); it != mData->constEnd(); ++it) {
15187 if (QCP::isInvalidData(it.value().key, it.value().value) ||
15188 QCP::isInvalidData(it.value().keyErrorPlus, it.value().keyErrorMinus) ||
15189 QCP::isInvalidData(it.value().valueErrorPlus,
15190 it.value().valueErrorPlus))
15191 qDebug() << Q_FUNC_INFO << "Data point at" << it.key() << "invalid."
15192 << "Plottable name:" << name();
15193 }
15194 #endif
15195
15196 // draw fill of graph:
15197 if (mLineStyle != lsNone) drawFill(painter, lineData);
15198
15199 // draw line:
15200 if (mLineStyle == lsImpulse)
15201 drawImpulsePlot(painter, lineData);
15202 else if (mLineStyle != lsNone)
15203 drawLinePlot(painter,
15204 lineData); // also step plots can be drawn as a line plot
15205
15206 // draw scatters:
15207 if (scatterData) drawScatterPlot(painter, scatterData);
15208
15209 // free allocated line and point vectors:
15210 delete lineData;
15211 if (scatterData) delete scatterData;
15212 }
15213
15214 /* inherits documentation from base class */
15215 void QCPGraph::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const {
15216 // draw fill:
15217 if (mBrush.style() != Qt::NoBrush) {
15218 applyFillAntialiasingHint(painter);
15219 painter->fillRect(QRectF(rect.left(), rect.top() + rect.height() / 2.0,
15220 rect.width(), rect.height() / 3.0),
15221 mBrush);
15222 }
15223 // draw line vertically centered:
15224 if (mLineStyle != lsNone) {
15225 applyDefaultAntialiasingHint(painter);
15226 painter->setPen(mPen);
15227 painter->drawLine(QLineF(
15228 rect.left(), rect.top() + rect.height() / 2.0, rect.right() + 5,
15229 rect.top() + rect.height() / 2.0)); // +5 on x2 else last segment is
15230 // missing from dashed/dotted pens
15231 }
15232 // draw scatter symbol:
15233 if (!mScatterStyle.isNone()) {
15234 applyScattersAntialiasingHint(painter);
15235 // scale scatter pixmap if it's too large to fit in legend icon rect:
15236 if (mScatterStyle.shape() == QCPScatterStyle::ssPixmap &&
15237 (mScatterStyle.pixmap().size().width() > rect.width() ||
15238 mScatterStyle.pixmap().size().height() > rect.height())) {
15239 QCPScatterStyle scaledStyle(mScatterStyle);
15240 scaledStyle.setPixmap(scaledStyle.pixmap().scaled(
15241 rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
15242 scaledStyle.applyTo(painter, mPen);
15243 scaledStyle.drawShape(painter, QRectF(rect).center());
15244 } else {
15245 mScatterStyle.applyTo(painter, mPen);
15246 mScatterStyle.drawShape(painter, QRectF(rect).center());
15247 }
15248 }
15249 }
15250
15251 /*! \internal
15252
15253 This function branches out to the line style specific "get(...)PlotData"
15254 functions, according to the line style of the graph.
15255
15256 \a lineData will be filled with raw points that will be drawn with the
15257 according draw functions, e.g. \ref drawLinePlot and \ref drawImpulsePlot.
15258 These aren't necessarily the original data points, since for step plots for
15259 example, additional points are needed for drawing lines that make up steps. If
15260 the line style of the graph is \ref lsNone, the \a lineData vector will be
15261 left untouched.
15262
15263 \a scatterData will be filled with the original data points so \ref
15264 drawScatterPlot can draw the scatter symbols accordingly. If no scatters need
15265 to be drawn, i.e. the scatter style's shape is \ref QCPScatterStyle::ssNone,
15266 pass 0 as \a scatterData, and this step will be skipped.
15267
15268 \see getScatterPlotData, getLinePlotData, getStepLeftPlotData,
15269 getStepRightPlotData, getStepCenterPlotData, getImpulsePlotData
15270 */
15271 void QCPGraph::getPlotData(QVector<QPointF> *lineData,
15272 QVector<QCPData> *scatterData) const {
15273 switch (mLineStyle) {
15274 case lsNone:
15275 getScatterPlotData(scatterData);
15276 break;
15277 case lsLine:
15278 getLinePlotData(lineData, scatterData);
15279 break;
15280 case lsStepLeft:
15281 getStepLeftPlotData(lineData, scatterData);
15282 break;
15283 case lsStepRight:
15284 getStepRightPlotData(lineData, scatterData);
15285 break;
15286 case lsStepCenter:
15287 getStepCenterPlotData(lineData, scatterData);
15288 break;
15289 case lsImpulse:
15290 getImpulsePlotData(lineData, scatterData);
15291 break;
15292 }
15293 }
15294
15295 /*! \internal
15296
15297 If line style is \ref lsNone and the scatter style's shape is not \ref
15298 QCPScatterStyle::ssNone, this function serves at providing the visible data
15299 points in \a scatterData, so the \ref drawScatterPlot function can draw the
15300 scatter points accordingly.
15301
15302 If line style is not \ref lsNone, this function is not called and the data for
15303 the scatter points are (if needed) calculated inside the corresponding other
15304 "get(...)PlotData" functions.
15305
15306 \see drawScatterPlot
15307 */
15308 void QCPGraph::getScatterPlotData(QVector<QCPData> *scatterData) const {
15309 getPreparedData(0, scatterData);
15310 }
15311
15312 /*! \internal
15313
15314 Places the raw data points needed for a normal linearly connected graph in \a
15315 linePixelData.
15316
15317 As for all plot data retrieval functions, \a scatterData just contains all
15318 unaltered data (scatter) points that are visible for drawing scatter points,
15319 if necessary. If drawing scatter points is disabled (i.e. the scatter style's
15320 shape is \ref QCPScatterStyle::ssNone), pass 0 as \a scatterData, and the
15321 function will skip filling the vector.
15322
15323 \see drawLinePlot
15324 */
15325 void QCPGraph::getLinePlotData(QVector<QPointF> *linePixelData,
15326 QVector<QCPData> *scatterData) const {
15327 QCPAxis *keyAxis = mKeyAxis.data();
15328 QCPAxis *valueAxis = mValueAxis.data();
15329 if (!keyAxis || !valueAxis) {
15330 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
15331 return;
15332 }
15333 if (!linePixelData) {
15334 qDebug() << Q_FUNC_INFO << "null pointer passed as linePixelData";
15335 return;
15336 }
15337
15338 QVector<QCPData> lineData;
15339 getPreparedData(&lineData, scatterData);
15340 linePixelData->reserve(lineData.size() +
15341 2); // added 2 to reserve memory for lower/upper fill
15342 // base points that might be needed for fill
15343 linePixelData->resize(lineData.size());
15344
15345 // transform lineData points to pixels:
15346 if (keyAxis->orientation() == Qt::Vertical) {
15347 for (int i = 0; i < lineData.size(); ++i) {
15348 (*linePixelData)[i].setX(valueAxis->coordToPixel(lineData.at(i).value));
15349 (*linePixelData)[i].setY(keyAxis->coordToPixel(lineData.at(i).key));
15350 }
15351 } else // key axis is horizontal
15352 {
15353 for (int i = 0; i < lineData.size(); ++i) {
15354 (*linePixelData)[i].setX(keyAxis->coordToPixel(lineData.at(i).key));
15355 (*linePixelData)[i].setY(valueAxis->coordToPixel(lineData.at(i).value));
15356 }
15357 }
15358 }
15359
15360 /*!
15361 \internal
15362 Places the raw data points needed for a step plot with left oriented steps in
15363 \a lineData.
15364
15365 As for all plot data retrieval functions, \a scatterData just contains all
15366 unaltered data (scatter) points that are visible for drawing scatter points,
15367 if necessary. If drawing scatter points is disabled (i.e. the scatter style's
15368 shape is \ref QCPScatterStyle::ssNone), pass 0 as \a scatterData, and the
15369 function will skip filling the vector.
15370
15371 \see drawLinePlot
15372 */
15373 void QCPGraph::getStepLeftPlotData(QVector<QPointF> *linePixelData,
15374 QVector<QCPData> *scatterData) const {
15375 QCPAxis *keyAxis = mKeyAxis.data();
15376 QCPAxis *valueAxis = mValueAxis.data();
15377 if (!keyAxis || !valueAxis) {
15378 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
15379 return;
15380 }
15381 if (!linePixelData) {
15382 qDebug() << Q_FUNC_INFO << "null pointer passed as lineData";
15383 return;
15384 }
15385
15386 QVector<QCPData> lineData;
15387 getPreparedData(&lineData, scatterData);
15388 linePixelData->reserve(lineData.size() * 2 +
15389 2); // added 2 to reserve memory for lower/upper fill
15390 // base points that might be needed for fill
15391 linePixelData->resize(lineData.size() * 2);
15392
15393 // calculate steps from lineData and transform to pixel coordinates:
15394 if (keyAxis->orientation() == Qt::Vertical) {
15395 double lastValue = valueAxis->coordToPixel(lineData.first().value);
15396 double key;
15397 for (int i = 0; i < lineData.size(); ++i) {
15398 key = keyAxis->coordToPixel(lineData.at(i).key);
15399 (*linePixelData)[i * 2 + 0].setX(lastValue);
15400 (*linePixelData)[i * 2 + 0].setY(key);
15401 lastValue = valueAxis->coordToPixel(lineData.at(i).value);
15402 (*linePixelData)[i * 2 + 1].setX(lastValue);
15403 (*linePixelData)[i * 2 + 1].setY(key);
15404 }
15405 } else // key axis is horizontal
15406 {
15407 double lastValue = valueAxis->coordToPixel(lineData.first().value);
15408 double key;
15409 for (int i = 0; i < lineData.size(); ++i) {
15410 key = keyAxis->coordToPixel(lineData.at(i).key);
15411 (*linePixelData)[i * 2 + 0].setX(key);
15412 (*linePixelData)[i * 2 + 0].setY(lastValue);
15413 lastValue = valueAxis->coordToPixel(lineData.at(i).value);
15414 (*linePixelData)[i * 2 + 1].setX(key);
15415 (*linePixelData)[i * 2 + 1].setY(lastValue);
15416 }
15417 }
15418 }
15419
15420 /*!
15421 \internal
15422 Places the raw data points needed for a step plot with right oriented steps in
15423 \a lineData.
15424
15425 As for all plot data retrieval functions, \a scatterData just contains all
15426 unaltered data (scatter) points that are visible for drawing scatter points,
15427 if necessary. If drawing scatter points is disabled (i.e. the scatter style's
15428 shape is \ref QCPScatterStyle::ssNone), pass 0 as \a scatterData, and the
15429 function will skip filling the vector.
15430
15431 \see drawLinePlot
15432 */
15433 void QCPGraph::getStepRightPlotData(QVector<QPointF> *linePixelData,
15434 QVector<QCPData> *scatterData) const {
15435 QCPAxis *keyAxis = mKeyAxis.data();
15436 QCPAxis *valueAxis = mValueAxis.data();
15437 if (!keyAxis || !valueAxis) {
15438 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
15439 return;
15440 }
15441 if (!linePixelData) {
15442 qDebug() << Q_FUNC_INFO << "null pointer passed as lineData";
15443 return;
15444 }
15445
15446 QVector<QCPData> lineData;
15447 getPreparedData(&lineData, scatterData);
15448 linePixelData->reserve(lineData.size() * 2 +
15449 2); // added 2 to reserve memory for lower/upper fill
15450 // base points that might be needed for fill
15451 linePixelData->resize(lineData.size() * 2);
15452
15453 // calculate steps from lineData and transform to pixel coordinates:
15454 if (keyAxis->orientation() == Qt::Vertical) {
15455 double lastKey = keyAxis->coordToPixel(lineData.first().key);
15456 double value;
15457 for (int i = 0; i < lineData.size(); ++i) {
15458 value = valueAxis->coordToPixel(lineData.at(i).value);
15459 (*linePixelData)[i * 2 + 0].setX(value);
15460 (*linePixelData)[i * 2 + 0].setY(lastKey);
15461 lastKey = keyAxis->coordToPixel(lineData.at(i).key);
15462 (*linePixelData)[i * 2 + 1].setX(value);
15463 (*linePixelData)[i * 2 + 1].setY(lastKey);
15464 }
15465 } else // key axis is horizontal
15466 {
15467 double lastKey = keyAxis->coordToPixel(lineData.first().key);
15468 double value;
15469 for (int i = 0; i < lineData.size(); ++i) {
15470 value = valueAxis->coordToPixel(lineData.at(i).value);
15471 (*linePixelData)[i * 2 + 0].setX(lastKey);
15472 (*linePixelData)[i * 2 + 0].setY(value);
15473 lastKey = keyAxis->coordToPixel(lineData.at(i).key);
15474 (*linePixelData)[i * 2 + 1].setX(lastKey);
15475 (*linePixelData)[i * 2 + 1].setY(value);
15476 }
15477 }
15478 }
15479
15480 /*!
15481 \internal
15482 Places the raw data points needed for a step plot with centered steps in \a
15483 lineData.
15484
15485 As for all plot data retrieval functions, \a scatterData just contains all
15486 unaltered data (scatter) points that are visible for drawing scatter points,
15487 if necessary. If drawing scatter points is disabled (i.e. the scatter style's
15488 shape is \ref QCPScatterStyle::ssNone), pass 0 as \a scatterData, and the
15489 function will skip filling the vector.
15490
15491 \see drawLinePlot
15492 */
15493 void QCPGraph::getStepCenterPlotData(QVector<QPointF> *linePixelData,
15494 QVector<QCPData> *scatterData) const {
15495 QCPAxis *keyAxis = mKeyAxis.data();
15496 QCPAxis *valueAxis = mValueAxis.data();
15497 if (!keyAxis || !valueAxis) {
15498 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
15499 return;
15500 }
15501 if (!linePixelData) {
15502 qDebug() << Q_FUNC_INFO << "null pointer passed as lineData";
15503 return;
15504 }
15505
15506 QVector<QCPData> lineData;
15507 getPreparedData(&lineData, scatterData);
15508 linePixelData->reserve(lineData.size() * 2 +
15509 2); // added 2 to reserve memory for lower/upper fill
15510 // base points that might be needed for fill
15511 linePixelData->resize(lineData.size() * 2);
15512 // calculate steps from lineData and transform to pixel coordinates:
15513 if (keyAxis->orientation() == Qt::Vertical) {
15514 double lastKey = keyAxis->coordToPixel(lineData.first().key);
15515 double lastValue = valueAxis->coordToPixel(lineData.first().value);
15516 double key;
15517 (*linePixelData)[0].setX(lastValue);
15518 (*linePixelData)[0].setY(lastKey);
15519 for (int i = 1; i < lineData.size(); ++i) {
15520 key = (keyAxis->coordToPixel(lineData.at(i).key) + lastKey) * 0.5;
15521 (*linePixelData)[i * 2 - 1].setX(lastValue);
15522 (*linePixelData)[i * 2 - 1].setY(key);
15523 lastValue = valueAxis->coordToPixel(lineData.at(i).value);
15524 lastKey = keyAxis->coordToPixel(lineData.at(i).key);
15525 (*linePixelData)[i * 2 + 0].setX(lastValue);
15526 (*linePixelData)[i * 2 + 0].setY(key);
15527 }
15528 (*linePixelData)[lineData.size() * 2 - 1].setX(lastValue);
15529 (*linePixelData)[lineData.size() * 2 - 1].setY(lastKey);
15530 } else // key axis is horizontal
15531 {
15532 double lastKey = keyAxis->coordToPixel(lineData.first().key);
15533 double lastValue = valueAxis->coordToPixel(lineData.first().value);
15534 double key;
15535 (*linePixelData)[0].setX(lastKey);
15536 (*linePixelData)[0].setY(lastValue);
15537 for (int i = 1; i < lineData.size(); ++i) {
15538 key = (keyAxis->coordToPixel(lineData.at(i).key) + lastKey) * 0.5;
15539 (*linePixelData)[i * 2 - 1].setX(key);
15540 (*linePixelData)[i * 2 - 1].setY(lastValue);
15541 lastValue = valueAxis->coordToPixel(lineData.at(i).value);
15542 lastKey = keyAxis->coordToPixel(lineData.at(i).key);
15543 (*linePixelData)[i * 2 + 0].setX(key);
15544 (*linePixelData)[i * 2 + 0].setY(lastValue);
15545 }
15546 (*linePixelData)[lineData.size() * 2 - 1].setX(lastKey);
15547 (*linePixelData)[lineData.size() * 2 - 1].setY(lastValue);
15548 }
15549 }
15550
15551 /*!
15552 \internal
15553 Places the raw data points needed for an impulse plot in \a lineData.
15554
15555 As for all plot data retrieval functions, \a scatterData just contains all
15556 unaltered data (scatter) points that are visible for drawing scatter points,
15557 if necessary. If drawing scatter points is disabled (i.e. the scatter style's
15558 shape is \ref QCPScatterStyle::ssNone), pass 0 as \a scatterData, and the
15559 function will skip filling the vector.
15560
15561 \see drawImpulsePlot
15562 */
15563 void QCPGraph::getImpulsePlotData(QVector<QPointF> *linePixelData,
15564 QVector<QCPData> *scatterData) const {
15565 QCPAxis *keyAxis = mKeyAxis.data();
15566 QCPAxis *valueAxis = mValueAxis.data();
15567 if (!keyAxis || !valueAxis) {
15568 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
15569 return;
15570 }
15571 if (!linePixelData) {
15572 qDebug() << Q_FUNC_INFO << "null pointer passed as linePixelData";
15573 return;
15574 }
15575
15576 QVector<QCPData> lineData;
15577 getPreparedData(&lineData, scatterData);
15578 linePixelData->resize(
15579 lineData.size() *
15580 2); // no need to reserve 2 extra points because impulse plot has no fill
15581
15582 // transform lineData points to pixels:
15583 if (keyAxis->orientation() == Qt::Vertical) {
15584 double zeroPointX = valueAxis->coordToPixel(0);
15585 double key;
15586 for (int i = 0; i < lineData.size(); ++i) {
15587 key = keyAxis->coordToPixel(lineData.at(i).key);
15588 (*linePixelData)[i * 2 + 0].setX(zeroPointX);
15589 (*linePixelData)[i * 2 + 0].setY(key);
15590 (*linePixelData)[i * 2 + 1].setX(
15591 valueAxis->coordToPixel(lineData.at(i).value));
15592 (*linePixelData)[i * 2 + 1].setY(key);
15593 }
15594 } else // key axis is horizontal
15595 {
15596 double zeroPointY = valueAxis->coordToPixel(0);
15597 double key;
15598 for (int i = 0; i < lineData.size(); ++i) {
15599 key = keyAxis->coordToPixel(lineData.at(i).key);
15600 (*linePixelData)[i * 2 + 0].setX(key);
15601 (*linePixelData)[i * 2 + 0].setY(zeroPointY);
15602 (*linePixelData)[i * 2 + 1].setX(key);
15603 (*linePixelData)[i * 2 + 1].setY(
15604 valueAxis->coordToPixel(lineData.at(i).value));
15605 }
15606 }
15607 }
15608
15609 /*! \internal
15610
15611 Draws the fill of the graph with the specified brush.
15612
15613 If the fill is a normal fill towards the zero-value-line, only the \a lineData
15614 is required (and two extra points at the zero-value-line, which are added by
15615 \ref addFillBasePoints and removed by \ref removeFillBasePoints after the fill
15616 drawing is done).
15617
15618 If the fill is a channel fill between this QCPGraph and another QCPGraph
15619 (mChannelFillGraph), the more complex polygon is calculated with the \ref
15620 getChannelFillPolygon function.
15621
15622 \see drawLinePlot
15623 */
15624 void QCPGraph::drawFill(QCPPainter *painter, QVector<QPointF> *lineData) const {
15625 if (mLineStyle == lsImpulse)
15626 return; // fill doesn't make sense for impulse plot
15627 if (mainBrush().style() == Qt::NoBrush || mainBrush().color().alpha() == 0)
15628 return;
15629
15630 applyFillAntialiasingHint(painter);
15631 if (!mChannelFillGraph) {
15632 // draw base fill under graph, fill goes all the way to the zero-value-line:
15633 addFillBasePoints(lineData);
15634 painter->setPen(Qt::NoPen);
15635 painter->setBrush(mainBrush());
15636 painter->drawPolygon(QPolygonF(*lineData));
15637 removeFillBasePoints(lineData);
15638 } else {
15639 // draw channel fill between this graph and mChannelFillGraph:
15640 painter->setPen(Qt::NoPen);
15641 painter->setBrush(mainBrush());
15642 painter->drawPolygon(getChannelFillPolygon(lineData));
15643 }
15644 }
15645
15646 /*! \internal
15647
15648 Draws scatter symbols at every data point passed in \a scatterData. scatter
15649 symbols are independent of the line style and are always drawn if the scatter
15650 style's shape is not \ref QCPScatterStyle::ssNone. Hence, the \a scatterData
15651 vector is outputted by all "get(...)PlotData" functions, together with the
15652 (line style dependent) line data.
15653
15654 \see drawLinePlot, drawImpulsePlot
15655 */
15656 void QCPGraph::drawScatterPlot(QCPPainter *painter,
15657 QVector<QCPData> *scatterData) const {
15658 QCPAxis *keyAxis = mKeyAxis.data();
15659 QCPAxis *valueAxis = mValueAxis.data();
15660 if (!keyAxis || !valueAxis) {
15661 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
15662 return;
15663 }
15664
15665 // draw error bars:
15666 if (mErrorType != etNone) {
15667 applyErrorBarsAntialiasingHint(painter);
15668 painter->setPen(mErrorPen);
15669 if (keyAxis->orientation() == Qt::Vertical) {
15670 for (int i = 0; i < scatterData->size(); ++i)
15671 drawError(painter, valueAxis->coordToPixel(scatterData->at(i).value),
15672 keyAxis->coordToPixel(scatterData->at(i).key),
15673 scatterData->at(i));
15674 } else {
15675 for (int i = 0; i < scatterData->size(); ++i)
15676 drawError(painter, keyAxis->coordToPixel(scatterData->at(i).key),
15677 valueAxis->coordToPixel(scatterData->at(i).value),
15678 scatterData->at(i));
15679 }
15680 }
15681
15682 // draw scatter point symbols:
15683 applyScattersAntialiasingHint(painter);
15684 mScatterStyle.applyTo(painter, mPen);
15685 if (keyAxis->orientation() == Qt::Vertical) {
15686 for (int i = 0; i < scatterData->size(); ++i)
15687 if (!qIsNaN(scatterData->at(i).value))
15688 mScatterStyle.drawShape(
15689 painter, valueAxis->coordToPixel(scatterData->at(i).value),
15690 keyAxis->coordToPixel(scatterData->at(i).key));
15691 } else {
15692 for (int i = 0; i < scatterData->size(); ++i)
15693 if (!qIsNaN(scatterData->at(i).value))
15694 mScatterStyle.drawShape(
15695 painter, keyAxis->coordToPixel(scatterData->at(i).key),
15696 valueAxis->coordToPixel(scatterData->at(i).value));
15697 }
15698 }
15699
15700 /*! \internal
15701
15702 Draws line graphs from the provided data. It connects all points in \a
15703 lineData, which was created by one of the "get(...)PlotData" functions for
15704 line styles that require simple line connections between the point vector they
15705 create. These are for example \ref getLinePlotData, \ref getStepLeftPlotData,
15706 \ref getStepRightPlotData and \ref getStepCenterPlotData.
15707
15708 \see drawScatterPlot, drawImpulsePlot
15709 */
15710 void QCPGraph::drawLinePlot(QCPPainter *painter,
15711 QVector<QPointF> *lineData) const {
15712 // draw line of graph:
15713 if (mainPen().style() != Qt::NoPen && mainPen().color().alpha() != 0) {
15714 applyDefaultAntialiasingHint(painter);
15715 painter->setPen(mainPen());
15716 painter->setBrush(Qt::NoBrush);
15717
15718 /* Draws polyline in batches, currently not used:
15719 int p = 0;
15720 while (p < lineData->size())
15721 {
15722 int batch = qMin(25, lineData->size()-p);
15723 if (p != 0)
15724 {
15725 ++batch;
15726 --p; // to draw the connection lines between two batches
15727 }
15728 painter->drawPolyline(lineData->constData()+p, batch);
15729 p += batch;
15730 }
15731 */
15732
15733 // if drawing solid line and not in PDF, use much faster line drawing
15734 // instead of polyline:
15735 if (mParentPlot->plottingHints().testFlag(QCP::phFastPolylines) &&
15736 painter->pen().style() == Qt::SolidLine &&
15737 !painter->modes().testFlag(QCPPainter::pmVectorized) &&
15738 !painter->modes().testFlag(QCPPainter::pmNoCaching)) {
15739 int i = 0;
15740 bool lastIsNan = false;
15741 const int lineDataSize = lineData->size();
15742 while (i < lineDataSize &&
15743 (qIsNaN(lineData->at(i).y()) ||
15744 qIsNaN(lineData->at(i).x()))) // make sure first point is not NaN
15745 ++i;
15746 ++i; // because drawing works in 1 point retrospect
15747 while (i < lineDataSize) {
15748 if (!qIsNaN(lineData->at(i).y()) &&
15749 !qIsNaN(lineData->at(i).x())) // NaNs create a gap in the line
15750 {
15751 if (!lastIsNan)
15752 painter->drawLine(lineData->at(i - 1), lineData->at(i));
15753 else
15754 lastIsNan = false;
15755 } else
15756 lastIsNan = true;
15757 ++i;
15758 }
15759 } else {
15760 int segmentStart = 0;
15761 int i = 0;
15762 const int lineDataSize = lineData->size();
15763 while (i < lineDataSize) {
15764 if (qIsNaN(lineData->at(i).y()) || qIsNaN(lineData->at(i).x()) ||
15765 qIsInf(lineData->at(i)
15766 .y())) // NaNs create a gap in the line. Also filter
15767 // Infs which make drawPolyline block
15768 {
15769 painter->drawPolyline(
15770 lineData->constData() + segmentStart,
15771 i - segmentStart); // i, because we don't want to include the
15772 // current NaN point
15773 segmentStart = i + 1;
15774 }
15775 ++i;
15776 }
15777 // draw last segment:
15778 painter->drawPolyline(lineData->constData() + segmentStart,
15779 lineDataSize - segmentStart);
15780 }
15781 }
15782 }
15783
15784 /*! \internal
15785
15786 Draws impulses from the provided data, i.e. it connects all line pairs in \a
15787 lineData, which was created by \ref getImpulsePlotData.
15788
15789 \see drawScatterPlot, drawLinePlot
15790 */
15791 void QCPGraph::drawImpulsePlot(QCPPainter *painter,
15792 QVector<QPointF> *lineData) const {
15793 // draw impulses:
15794 if (mainPen().style() != Qt::NoPen && mainPen().color().alpha() != 0) {
15795 applyDefaultAntialiasingHint(painter);
15796 QPen pen = mainPen();
15797 pen.setCapStyle(
15798 Qt::FlatCap); // so impulse line doesn't reach beyond zero-line
15799 painter->setPen(pen);
15800 painter->setBrush(Qt::NoBrush);
15801 painter->drawLines(*lineData);
15802 }
15803 }
15804
15805 /*! \internal
15806
15807 Returns the \a lineData and \a scatterData that need to be plotted for this
15808 graph taking into consideration the current axis ranges and, if \ref
15809 setAdaptiveSampling is enabled, local point densities.
15810
15811 0 may be passed as \a lineData or \a scatterData to indicate that the
15812 respective dataset isn't needed. For example, if the scatter style (\ref
15813 setScatterStyle) is \ref QCPScatterStyle::ssNone, \a scatterData should be 0
15814 to prevent unnecessary calculations.
15815
15816 This method is used by the various "get(...)PlotData" methods to get the basic
15817 working set of data.
15818 */
15819 void QCPGraph::getPreparedData(QVector<QCPData> *lineData,
15820 QVector<QCPData> *scatterData) const {
15821 QCPAxis *keyAxis = mKeyAxis.data();
15822 QCPAxis *valueAxis = mValueAxis.data();
15823 if (!keyAxis || !valueAxis) {
15824 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
15825 return;
15826 }
15827 // get visible data range:
15828 QCPDataMap::const_iterator lower,
15829 upper; // note that upper is the actual upper point, and not 1 step after
15830 // the upper point
15831 getVisibleDataBounds(lower, upper);
15832 if (lower == mData->constEnd() || upper == mData->constEnd()) return;
15833
15834 // count points in visible range, taking into account that we only need to
15835 // count to the limit maxCount if using adaptive sampling:
15836 int maxCount = std::numeric_limits<int>::max();
15837 if (mAdaptiveSampling) {
15838 int keyPixelSpan = qAbs(keyAxis->coordToPixel(lower.key()) -
15839 keyAxis->coordToPixel(upper.key()));
15840 maxCount = 2 * keyPixelSpan + 2;
15841 }
15842 int dataCount = countDataInBounds(lower, upper, maxCount);
15843
15844 if (mAdaptiveSampling &&
15845 dataCount >= maxCount) // use adaptive sampling only if there are at
15846 // least two points per pixel on average
15847 {
15848 if (lineData) {
15849 QCPDataMap::const_iterator it = lower;
15850 QCPDataMap::const_iterator upperEnd = upper + 1;
15851 double minValue = it.value().value;
15852 double maxValue = it.value().value;
15853 QCPDataMap::const_iterator currentIntervalFirstPoint = it;
15854 int reversedFactor =
15855 keyAxis->rangeReversed() != (keyAxis->orientation() == Qt::Vertical)
15856 ? -1
15857 : 1; // is used to calculate keyEpsilon pixel into the correct
15858 // direction
15859 int reversedRound =
15860 keyAxis->rangeReversed() != (keyAxis->orientation() == Qt::Vertical)
15861 ? 1
15862 : 0; // is used to switch between floor (normal) and ceil
15863 // (reversed) rounding of currentIntervalStartKey
15864 double currentIntervalStartKey = keyAxis->pixelToCoord(
15865 (int)(keyAxis->coordToPixel(lower.key()) + reversedRound));
15866 double lastIntervalEndKey = currentIntervalStartKey;
15867 double keyEpsilon = qAbs(
15868 currentIntervalStartKey -
15869 keyAxis->pixelToCoord(
15870 keyAxis->coordToPixel(currentIntervalStartKey) +
15871 1.0 * reversedFactor)); // interval of one pixel on screen when
15872 // mapped to plot key coordinates
15873 bool keyEpsilonVariable =
15874 keyAxis->scaleType() ==
15875 QCPAxis::stLogarithmic; // indicates whether keyEpsilon needs to be
15876 // updated after every interval (for log
15877 // axes)
15878 int intervalDataCount = 1;
15879 ++it; // advance iterator to second data point because adaptive sampling
15880 // works in 1 point retrospect
15881 while (it != upperEnd) {
15882 if (it.key() < currentIntervalStartKey +
15883 keyEpsilon) // data point is still within same
15884 // pixel, so skip it and expand value
15885 // span of this cluster if necessary
15886 {
15887 if (it.value().value < minValue)
15888 minValue = it.value().value;
15889 else if (it.value().value > maxValue)
15890 maxValue = it.value().value;
15891 ++intervalDataCount;
15892 } else // new pixel interval started
15893 {
15894 if (intervalDataCount >= 2) // last pixel had multiple data points,
15895 // consolidate them to a cluster
15896 {
15897 if (lastIntervalEndKey <
15898 currentIntervalStartKey -
15899 keyEpsilon) // last point is further away, so first point
15900 // of this cluster must be at a real data point
15901 lineData->append(
15902 QCPData(currentIntervalStartKey + keyEpsilon * 0.2,
15903 currentIntervalFirstPoint.value().value));
15904 lineData->append(
15905 QCPData(currentIntervalStartKey + keyEpsilon * 0.25, minValue));
15906 lineData->append(
15907 QCPData(currentIntervalStartKey + keyEpsilon * 0.75, maxValue));
15908 if (it.key() >
15909 currentIntervalStartKey +
15910 keyEpsilon *
15911 2) // new pixel started further away from previous
15912 // cluster, so make sure the last point of the
15913 // cluster is at a real data point
15914 lineData->append(
15915 QCPData(currentIntervalStartKey + keyEpsilon * 0.8,
15916 (it - 1).value().value));
15917 } else
15918 lineData->append(QCPData(currentIntervalFirstPoint.key(),
15919 currentIntervalFirstPoint.value().value));
15920 lastIntervalEndKey = (it - 1).value().key;
15921 minValue = it.value().value;
15922 maxValue = it.value().value;
15923 currentIntervalFirstPoint = it;
15924 currentIntervalStartKey = keyAxis->pixelToCoord(
15925 (int)(keyAxis->coordToPixel(it.key()) + reversedRound));
15926 if (keyEpsilonVariable)
15927 keyEpsilon =
15928 qAbs(currentIntervalStartKey -
15929 keyAxis->pixelToCoord(
15930 keyAxis->coordToPixel(currentIntervalStartKey) +
15931 1.0 * reversedFactor));
15932 intervalDataCount = 1;
15933 }
15934 ++it;
15935 }
15936 // handle last interval:
15937 if (intervalDataCount >= 2) // last pixel had multiple data points,
15938 // consolidate them to a cluster
15939 {
15940 if (lastIntervalEndKey <
15941 currentIntervalStartKey -
15942 keyEpsilon) // last point wasn't a cluster, so first point of
15943 // this cluster must be at a real data point
15944 lineData->append(QCPData(currentIntervalStartKey + keyEpsilon * 0.2,
15945 currentIntervalFirstPoint.value().value));
15946 lineData->append(
15947 QCPData(currentIntervalStartKey + keyEpsilon * 0.25, minValue));
15948 lineData->append(
15949 QCPData(currentIntervalStartKey + keyEpsilon * 0.75, maxValue));
15950 } else
15951 lineData->append(QCPData(currentIntervalFirstPoint.key(),
15952 currentIntervalFirstPoint.value().value));
15953 }
15954
15955 if (scatterData) {
15956 double valueMaxRange = valueAxis->range().upper;
15957 double valueMinRange = valueAxis->range().lower;
15958 QCPDataMap::const_iterator it = lower;
15959 QCPDataMap::const_iterator upperEnd = upper + 1;
15960 double minValue = it.value().value;
15961 double maxValue = it.value().value;
15962 QCPDataMap::const_iterator minValueIt = it;
15963 QCPDataMap::const_iterator maxValueIt = it;
15964 QCPDataMap::const_iterator currentIntervalStart = it;
15965 int reversedFactor = keyAxis->rangeReversed()
15966 ? -1
15967 : 1; // is used to calculate keyEpsilon pixel
15968 // into the correct direction
15969 int reversedRound =
15970 keyAxis->rangeReversed()
15971 ? 1
15972 : 0; // is used to switch between floor (normal) and ceil
15973 // (reversed) rounding of currentIntervalStartKey
15974 double currentIntervalStartKey = keyAxis->pixelToCoord(
15975 (int)(keyAxis->coordToPixel(lower.key()) + reversedRound));
15976 double keyEpsilon = qAbs(
15977 currentIntervalStartKey -
15978 keyAxis->pixelToCoord(
15979 keyAxis->coordToPixel(currentIntervalStartKey) +
15980 1.0 * reversedFactor)); // interval of one pixel on screen when
15981 // mapped to plot key coordinates
15982 bool keyEpsilonVariable =
15983 keyAxis->scaleType() ==
15984 QCPAxis::stLogarithmic; // indicates whether keyEpsilon needs to be
15985 // updated after every interval (for log
15986 // axes)
15987 int intervalDataCount = 1;
15988 ++it; // advance iterator to second data point because adaptive sampling
15989 // works in 1 point retrospect
15990 while (it != upperEnd) {
15991 if (it.key() < currentIntervalStartKey +
15992 keyEpsilon) // data point is still within same
15993 // pixel, so skip it and expand value
15994 // span of this pixel if necessary
15995 {
15996 if (it.value().value < minValue && it.value().value > valueMinRange &&
15997 it.value().value < valueMaxRange) {
15998 minValue = it.value().value;
15999 minValueIt = it;
16000 } else if (it.value().value > maxValue &&
16001 it.value().value > valueMinRange &&
16002 it.value().value < valueMaxRange) {
16003 maxValue = it.value().value;
16004 maxValueIt = it;
16005 }
16006 ++intervalDataCount;
16007 } else // new pixel started
16008 {
16009 if (intervalDataCount >=
16010 2) // last pixel had multiple data points, consolidate them
16011 {
16012 // determine value pixel span and add as many points in interval to
16013 // maintain certain vertical data density (this is specific to
16014 // scatter plot):
16015 double valuePixelSpan = qAbs(valueAxis->coordToPixel(minValue) -
16016 valueAxis->coordToPixel(maxValue));
16017 int dataModulo =
16018 qMax(1, qRound(intervalDataCount /
16019 (valuePixelSpan /
16020 4.0))); // approximately every 4 value pixels
16021 // one data point on average
16022 QCPDataMap::const_iterator intervalIt = currentIntervalStart;
16023 int c = 0;
16024 while (intervalIt != it) {
16025 if ((c % dataModulo == 0 || intervalIt == minValueIt ||
16026 intervalIt == maxValueIt) &&
16027 intervalIt.value().value > valueMinRange &&
16028 intervalIt.value().value < valueMaxRange)
16029 scatterData->append(intervalIt.value());
16030 ++c;
16031 ++intervalIt;
16032 }
16033 } else if (currentIntervalStart.value().value > valueMinRange &&
16034 currentIntervalStart.value().value < valueMaxRange)
16035 scatterData->append(currentIntervalStart.value());
16036 minValue = it.value().value;
16037 maxValue = it.value().value;
16038 currentIntervalStart = it;
16039 currentIntervalStartKey = keyAxis->pixelToCoord(
16040 (int)(keyAxis->coordToPixel(it.key()) + reversedRound));
16041 if (keyEpsilonVariable)
16042 keyEpsilon =
16043 qAbs(currentIntervalStartKey -
16044 keyAxis->pixelToCoord(
16045 keyAxis->coordToPixel(currentIntervalStartKey) +
16046 1.0 * reversedFactor));
16047 intervalDataCount = 1;
16048 }
16049 ++it;
16050 }
16051 // handle last interval:
16052 if (intervalDataCount >=
16053 2) // last pixel had multiple data points, consolidate them
16054 {
16055 // determine value pixel span and add as many points in interval to
16056 // maintain certain vertical data density (this is specific to scatter
16057 // plot):
16058 double valuePixelSpan = qAbs(valueAxis->coordToPixel(minValue) -
16059 valueAxis->coordToPixel(maxValue));
16060 int dataModulo =
16061 qMax(1, qRound(intervalDataCount /
16062 (valuePixelSpan /
16063 4.0))); // approximately every 4 value pixels one
16064 // data point on average
16065 QCPDataMap::const_iterator intervalIt = currentIntervalStart;
16066 int c = 0;
16067 while (intervalIt != it) {
16068 if ((c % dataModulo == 0 || intervalIt == minValueIt ||
16069 intervalIt == maxValueIt) &&
16070 intervalIt.value().value > valueMinRange &&
16071 intervalIt.value().value < valueMaxRange)
16072 scatterData->append(intervalIt.value());
16073 ++c;
16074 ++intervalIt;
16075 }
16076 } else if (currentIntervalStart.value().value > valueMinRange &&
16077 currentIntervalStart.value().value < valueMaxRange)
16078 scatterData->append(currentIntervalStart.value());
16079 }
16080 } else // don't use adaptive sampling algorithm, transfer points one-to-one
16081 // from the map into the output parameters
16082 {
16083 QVector<QCPData> *dataVector = 0;
16084 if (lineData)
16085 dataVector = lineData;
16086 else if (scatterData)
16087 dataVector = scatterData;
16088 if (dataVector) {
16089 QCPDataMap::const_iterator it = lower;
16090 QCPDataMap::const_iterator upperEnd = upper + 1;
16091 dataVector->reserve(dataCount + 2); // +2 for possible fill end points
16092 while (it != upperEnd) {
16093 dataVector->append(it.value());
16094 ++it;
16095 }
16096 }
16097 if (lineData && scatterData) *scatterData = *dataVector;
16098 }
16099 }
16100
16101 /*! \internal
16102
16103 called by the scatter drawing function (\ref drawScatterPlot) to draw the
16104 error bars on one data point. \a x and \a y pixel positions of the data point
16105 are passed since they are already known in pixel coordinates in the drawing
16106 function, so we save some extra coordToPixel transforms here. \a data is
16107 therefore only used for the errors, not key and value.
16108 */
16109 void QCPGraph::drawError(QCPPainter *painter, double x, double y,
16110 const QCPData &data) const {
16111 if (qIsNaN(data.value)) return;
16112 QCPAxis *keyAxis = mKeyAxis.data();
16113 QCPAxis *valueAxis = mValueAxis.data();
16114 if (!keyAxis || !valueAxis) {
16115 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
16116 return;
16117 }
16118
16119 double a, b; // positions of error bar bounds in pixels
16120 double barWidthHalf = mErrorBarSize * 0.5;
16121 double skipSymbolMargin =
16122 mScatterStyle.size(); // pixels left blank per side, when
16123 // mErrorBarSkipSymbol is true
16124
16125 if (keyAxis->orientation() == Qt::Vertical) {
16126 // draw key error vertically and value error horizontally
16127 if (mErrorType == etKey || mErrorType == etBoth) {
16128 a = keyAxis->coordToPixel(data.key - data.keyErrorMinus);
16129 b = keyAxis->coordToPixel(data.key + data.keyErrorPlus);
16130 if (keyAxis->rangeReversed()) qSwap(a, b);
16131 // draw spine:
16132 if (mErrorBarSkipSymbol) {
16133 if (a - y > skipSymbolMargin) // don't draw spine if error is so small
16134 // it's within skipSymbolmargin
16135 painter->drawLine(QLineF(x, a, x, y + skipSymbolMargin));
16136 if (y - b > skipSymbolMargin)
16137 painter->drawLine(QLineF(x, y - skipSymbolMargin, x, b));
16138 } else
16139 painter->drawLine(QLineF(x, a, x, b));
16140 // draw handles:
16141 painter->drawLine(QLineF(x - barWidthHalf, a, x + barWidthHalf, a));
16142 painter->drawLine(QLineF(x - barWidthHalf, b, x + barWidthHalf, b));
16143 }
16144 if (mErrorType == etValue || mErrorType == etBoth) {
16145 a = valueAxis->coordToPixel(data.value - data.valueErrorMinus);
16146 b = valueAxis->coordToPixel(data.value + data.valueErrorPlus);
16147 if (valueAxis->rangeReversed()) qSwap(a, b);
16148 // draw spine:
16149 if (mErrorBarSkipSymbol) {
16150 if (x - a > skipSymbolMargin) // don't draw spine if error is so small
16151 // it's within skipSymbolmargin
16152 painter->drawLine(QLineF(a, y, x - skipSymbolMargin, y));
16153 if (b - x > skipSymbolMargin)
16154 painter->drawLine(QLineF(x + skipSymbolMargin, y, b, y));
16155 } else
16156 painter->drawLine(QLineF(a, y, b, y));
16157 // draw handles:
16158 painter->drawLine(QLineF(a, y - barWidthHalf, a, y + barWidthHalf));
16159 painter->drawLine(QLineF(b, y - barWidthHalf, b, y + barWidthHalf));
16160 }
16161 } else // mKeyAxis->orientation() is Qt::Horizontal
16162 {
16163 // draw value error vertically and key error horizontally
16164 if (mErrorType == etKey || mErrorType == etBoth) {
16165 a = keyAxis->coordToPixel(data.key - data.keyErrorMinus);
16166 b = keyAxis->coordToPixel(data.key + data.keyErrorPlus);
16167 if (keyAxis->rangeReversed()) qSwap(a, b);
16168 // draw spine:
16169 if (mErrorBarSkipSymbol) {
16170 if (x - a > skipSymbolMargin) // don't draw spine if error is so small
16171 // it's within skipSymbolmargin
16172 painter->drawLine(QLineF(a, y, x - skipSymbolMargin, y));
16173 if (b - x > skipSymbolMargin)
16174 painter->drawLine(QLineF(x + skipSymbolMargin, y, b, y));
16175 } else
16176 painter->drawLine(QLineF(a, y, b, y));
16177 // draw handles:
16178 painter->drawLine(QLineF(a, y - barWidthHalf, a, y + barWidthHalf));
16179 painter->drawLine(QLineF(b, y - barWidthHalf, b, y + barWidthHalf));
16180 }
16181 if (mErrorType == etValue || mErrorType == etBoth) {
16182 a = valueAxis->coordToPixel(data.value - data.valueErrorMinus);
16183 b = valueAxis->coordToPixel(data.value + data.valueErrorPlus);
16184 if (valueAxis->rangeReversed()) qSwap(a, b);
16185 // draw spine:
16186 if (mErrorBarSkipSymbol) {
16187 if (a - y > skipSymbolMargin) // don't draw spine if error is so small
16188 // it's within skipSymbolmargin
16189 painter->drawLine(QLineF(x, a, x, y + skipSymbolMargin));
16190 if (y - b > skipSymbolMargin)
16191 painter->drawLine(QLineF(x, y - skipSymbolMargin, x, b));
16192 } else
16193 painter->drawLine(QLineF(x, a, x, b));
16194 // draw handles:
16195 painter->drawLine(QLineF(x - barWidthHalf, a, x + barWidthHalf, a));
16196 painter->drawLine(QLineF(x - barWidthHalf, b, x + barWidthHalf, b));
16197 }
16198 }
16199 }
16200
16201 /*! \internal
16202
16203 called by \ref getPreparedData to determine which data (key) range is visible
16204 at the current key axis range setting, so only that needs to be processed.
16205
16206 \a lower returns an iterator to the lowest data point that needs to be taken
16207 into account when plotting. Note that in order to get a clean plot all the way
16208 to the edge of the axis rect, \a lower may still be just outside the visible
16209 range.
16210
16211 \a upper returns an iterator to the highest data point. Same as before, \a
16212 upper may also lie just outside of the visible range.
16213
16214 if the graph contains no data, both \a lower and \a upper point to constEnd.
16215 */
16216 void QCPGraph::getVisibleDataBounds(QCPDataMap::const_iterator &lower,
16217 QCPDataMap::const_iterator &upper) const {
16218 if (!mKeyAxis) {
16219 qDebug() << Q_FUNC_INFO << "invalid key axis";
16220 return;
16221 }
16222 if (mData->isEmpty()) {
16223 lower = mData->constEnd();
16224 upper = mData->constEnd();
16225 return;
16226 }
16227
16228 // get visible data range as QMap iterators
16229 QCPDataMap::const_iterator lbound =
16230 mData->lowerBound(mKeyAxis.data()->range().lower);
16231 QCPDataMap::const_iterator ubound =
16232 mData->upperBound(mKeyAxis.data()->range().upper);
16233 bool lowoutlier =
16234 lbound != mData->constBegin(); // indicates whether there exist points
16235 // below axis range
16236 bool highoutlier =
16237 ubound != mData->constEnd(); // indicates whether there exist points
16238 // above axis range
16239
16240 lower =
16241 (lowoutlier ? lbound - 1
16242 : lbound); // data point range that will be actually drawn
16243 upper = (highoutlier
16244 ? ubound
16245 : ubound - 1); // data point range that will be actually drawn
16246 }
16247
16248 /*! \internal
16249
16250 Counts the number of data points between \a lower and \a upper (including
16251 them), up to a maximum of \a maxCount.
16252
16253 This function is used by \ref getPreparedData to determine whether adaptive
16254 sampling shall be used (if enabled via \ref setAdaptiveSampling) or not. This
16255 is also why counting of data points only needs to be done until \a maxCount is
16256 reached, which should be set to the number of data points at which adaptive
16257 sampling sets in.
16258 */
16259 int QCPGraph::countDataInBounds(const QCPDataMap::const_iterator &lower,
16260 const QCPDataMap::const_iterator &upper,
16261 int maxCount) const {
16262 if (upper == mData->constEnd() && lower == mData->constEnd()) return 0;
16263 QCPDataMap::const_iterator it = lower;
16264 int count = 1;
16265 while (it != upper && count < maxCount) {
16266 ++it;
16267 ++count;
16268 }
16269 return count;
16270 }
16271
16272 /*! \internal
16273
16274 The line data vector generated by e.g. getLinePlotData contains only the line
16275 that connects the data points. If the graph needs to be filled, two additional
16276 points need to be added at the value-zero-line in the lower and upper key
16277 positions of the graph. This function calculates these points and adds them to
16278 the end of \a lineData. Since the fill is typically drawn before the line
16279 stroke, these added points need to be removed again after the fill is done,
16280 with the removeFillBasePoints function.
16281
16282 The expanding of \a lineData by two points will not cause unnecessary memory
16283 reallocations, because the data vector generation functions (getLinePlotData
16284 etc.) reserve two extra points when they allocate memory for \a lineData.
16285
16286 \see removeFillBasePoints, lowerFillBasePoint, upperFillBasePoint
16287 */
16288 void QCPGraph::addFillBasePoints(QVector<QPointF> *lineData) const {
16289 if (!mKeyAxis) {
16290 qDebug() << Q_FUNC_INFO << "invalid key axis";
16291 return;
16292 }
16293 if (!lineData) {
16294 qDebug() << Q_FUNC_INFO << "passed null as lineData";
16295 return;
16296 }
16297 if (lineData->isEmpty()) return;
16298
16299 // append points that close the polygon fill at the key axis:
16300 if (mKeyAxis.data()->orientation() == Qt::Vertical) {
16301 *lineData << upperFillBasePoint(lineData->last().y());
16302 *lineData << lowerFillBasePoint(lineData->first().y());
16303 } else {
16304 *lineData << upperFillBasePoint(lineData->last().x());
16305 *lineData << lowerFillBasePoint(lineData->first().x());
16306 }
16307 }
16308
16309 /*! \internal
16310
16311 removes the two points from \a lineData that were added by \ref
16312 addFillBasePoints.
16313
16314 \see addFillBasePoints, lowerFillBasePoint, upperFillBasePoint
16315 */
16316 void QCPGraph::removeFillBasePoints(QVector<QPointF> *lineData) const {
16317 if (!lineData) {
16318 qDebug() << Q_FUNC_INFO << "passed null as lineData";
16319 return;
16320 }
16321 if (lineData->isEmpty()) return;
16322
16323 lineData->remove(lineData->size() - 2, 2);
16324 }
16325
16326 /*! \internal
16327
16328 called by \ref addFillBasePoints to conveniently assign the point which closes
16329 the fill polygon on the lower side of the zero-value-line parallel to the key
16330 axis. The logarithmic axis scale case is a bit special, since the
16331 zero-value-line in pixel coordinates is in positive or negative infinity. So
16332 this case is handled separately by just closing the fill polygon on the axis
16333 which lies in the direction towards the zero value.
16334
16335 \a lowerKey will be the the key (in pixels) of the returned point. Depending
16336 on whether the key axis is horizontal or vertical, \a lowerKey will end up as
16337 the x or y value of the returned point, respectively.
16338
16339 \see upperFillBasePoint, addFillBasePoints
16340 */
16341 QPointF QCPGraph::lowerFillBasePoint(double lowerKey) const {
16342 QCPAxis *keyAxis = mKeyAxis.data();
16343 QCPAxis *valueAxis = mValueAxis.data();
16344 if (!keyAxis || !valueAxis) {
16345 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
16346 return QPointF();
16347 }
16348
16349 QPointF point;
16350 if (valueAxis->scaleType() == QCPAxis::stLinear) {
16351 if (keyAxis->axisType() == QCPAxis::atLeft) {
16352 point.setX(valueAxis->coordToPixel(0));
16353 point.setY(lowerKey);
16354 } else if (keyAxis->axisType() == QCPAxis::atRight) {
16355 point.setX(valueAxis->coordToPixel(0));
16356 point.setY(lowerKey);
16357 } else if (keyAxis->axisType() == QCPAxis::atTop) {
16358 point.setX(lowerKey);
16359 point.setY(valueAxis->coordToPixel(0));
16360 } else if (keyAxis->axisType() == QCPAxis::atBottom) {
16361 point.setX(lowerKey);
16362 point.setY(valueAxis->coordToPixel(0));
16363 }
16364 } else // valueAxis->mScaleType == QCPAxis::stLogarithmic
16365 {
16366 // In logarithmic scaling we can't just draw to value zero so we just fill
16367 // all the way to the axis which is in the direction towards zero
16368 if (keyAxis->orientation() == Qt::Vertical) {
16369 if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) ||
16370 (valueAxis->range().upper > 0 &&
16371 valueAxis->rangeReversed())) // if range is negative, zero is on
16372 // opposite side of key axis
16373 point.setX(keyAxis->axisRect()->right());
16374 else
16375 point.setX(keyAxis->axisRect()->left());
16376 point.setY(lowerKey);
16377 } else if (keyAxis->axisType() == QCPAxis::atTop ||
16378 keyAxis->axisType() == QCPAxis::atBottom) {
16379 point.setX(lowerKey);
16380 if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) ||
16381 (valueAxis->range().upper > 0 &&
16382 valueAxis->rangeReversed())) // if range is negative, zero is on
16383 // opposite side of key axis
16384 point.setY(keyAxis->axisRect()->top());
16385 else
16386 point.setY(keyAxis->axisRect()->bottom());
16387 }
16388 }
16389 return point;
16390 }
16391
16392 /*! \internal
16393
16394 called by \ref addFillBasePoints to conveniently assign the point which closes
16395 the fill polygon on the upper side of the zero-value-line parallel to the key
16396 axis. The logarithmic axis scale case is a bit special, since the
16397 zero-value-line in pixel coordinates is in positive or negative infinity. So
16398 this case is handled separately by just closing the fill polygon on the axis
16399 which lies in the direction towards the zero value.
16400
16401 \a upperKey will be the the key (in pixels) of the returned point. Depending
16402 on whether the key axis is horizontal or vertical, \a upperKey will end up as
16403 the x or y value of the returned point, respectively.
16404
16405 \see lowerFillBasePoint, addFillBasePoints
16406 */
16407 QPointF QCPGraph::upperFillBasePoint(double upperKey) const {
16408 QCPAxis *keyAxis = mKeyAxis.data();
16409 QCPAxis *valueAxis = mValueAxis.data();
16410 if (!keyAxis || !valueAxis) {
16411 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
16412 return QPointF();
16413 }
16414
16415 QPointF point;
16416 if (valueAxis->scaleType() == QCPAxis::stLinear) {
16417 if (keyAxis->axisType() == QCPAxis::atLeft) {
16418 point.setX(valueAxis->coordToPixel(0));
16419 point.setY(upperKey);
16420 } else if (keyAxis->axisType() == QCPAxis::atRight) {
16421 point.setX(valueAxis->coordToPixel(0));
16422 point.setY(upperKey);
16423 } else if (keyAxis->axisType() == QCPAxis::atTop) {
16424 point.setX(upperKey);
16425 point.setY(valueAxis->coordToPixel(0));
16426 } else if (keyAxis->axisType() == QCPAxis::atBottom) {
16427 point.setX(upperKey);
16428 point.setY(valueAxis->coordToPixel(0));
16429 }
16430 } else // valueAxis->mScaleType == QCPAxis::stLogarithmic
16431 {
16432 // In logarithmic scaling we can't just draw to value 0 so we just fill all
16433 // the way to the axis which is in the direction towards 0
16434 if (keyAxis->orientation() == Qt::Vertical) {
16435 if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) ||
16436 (valueAxis->range().upper > 0 &&
16437 valueAxis->rangeReversed())) // if range is negative, zero is on
16438 // opposite side of key axis
16439 point.setX(keyAxis->axisRect()->right());
16440 else
16441 point.setX(keyAxis->axisRect()->left());
16442 point.setY(upperKey);
16443 } else if (keyAxis->axisType() == QCPAxis::atTop ||
16444 keyAxis->axisType() == QCPAxis::atBottom) {
16445 point.setX(upperKey);
16446 if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) ||
16447 (valueAxis->range().upper > 0 &&
16448 valueAxis->rangeReversed())) // if range is negative, zero is on
16449 // opposite side of key axis
16450 point.setY(keyAxis->axisRect()->top());
16451 else
16452 point.setY(keyAxis->axisRect()->bottom());
16453 }
16454 }
16455 return point;
16456 }
16457
16458 /*! \internal
16459
16460 Generates the polygon needed for drawing channel fills between this graph
16461 (data passed via \a lineData) and the graph specified by mChannelFillGraph
16462 (data generated by calling its \ref getPlotData function). May return an empty
16463 polygon if the key ranges have no overlap or fill target graph and this graph
16464 don't have same orientation (i.e. both key axes horizontal or both key axes
16465 vertical). For increased performance (due to implicit sharing), keep the
16466 returned QPolygonF const.
16467 */
16468 const QPolygonF QCPGraph::getChannelFillPolygon(
16469 const QVector<QPointF> *lineData) const {
16470 if (!mChannelFillGraph) return QPolygonF();
16471
16472 QCPAxis *keyAxis = mKeyAxis.data();
16473 QCPAxis *valueAxis = mValueAxis.data();
16474 if (!keyAxis || !valueAxis) {
16475 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
16476 return QPolygonF();
16477 }
16478 if (!mChannelFillGraph.data()->mKeyAxis) {
16479 qDebug() << Q_FUNC_INFO << "channel fill target key axis invalid";
16480 return QPolygonF();
16481 }
16482
16483 if (mChannelFillGraph.data()->mKeyAxis.data()->orientation() !=
16484 keyAxis->orientation())
16485 return QPolygonF(); // don't have same axis orientation, can't fill that
16486 // (Note: if keyAxis fits, valueAxis will fit too,
16487 // because it's always orthogonal to keyAxis)
16488
16489 if (lineData->isEmpty()) return QPolygonF();
16490 QVector<QPointF> otherData;
16491 mChannelFillGraph.data()->getPlotData(&otherData, 0);
16492 if (otherData.isEmpty()) return QPolygonF();
16493 QVector<QPointF> thisData;
16494 thisData.reserve(lineData->size() +
16495 otherData.size()); // because we will join both vectors at
16496 // end of this function
16497 for (int i = 0; i < lineData->size();
16498 ++i) // don't use the vector<<(vector), it squeezes internally, which
16499 // ruins the performance tuning with reserve()
16500 thisData << lineData->at(i);
16501
16502 // pointers to be able to swap them, depending which data range needs
16503 // cropping:
16504 QVector<QPointF> *staticData = &thisData;
16505 QVector<QPointF> *croppedData = &otherData;
16506
16507 // crop both vectors to ranges in which the keys overlap (which coord is key,
16508 // depends on axisType):
16509 if (keyAxis->orientation() == Qt::Horizontal) {
16510 // x is key
16511 // if an axis range is reversed, the data point keys will be descending.
16512 // Reverse them, since following algorithm assumes ascending keys:
16513 if (staticData->first().x() > staticData->last().x()) {
16514 int size = staticData->size();
16515 for (int i = 0; i < size / 2; ++i)
16516 qSwap((*staticData)[i], (*staticData)[size - 1 - i]);
16517 }
16518 if (croppedData->first().x() > croppedData->last().x()) {
16519 int size = croppedData->size();
16520 for (int i = 0; i < size / 2; ++i)
16521 qSwap((*croppedData)[i], (*croppedData)[size - 1 - i]);
16522 }
16523 // crop lower bound:
16524 if (staticData->first().x() <
16525 croppedData->first().x()) // other one must be cropped
16526 qSwap(staticData, croppedData);
16527 int lowBound = findIndexBelowX(croppedData, staticData->first().x());
16528 if (lowBound == -1) return QPolygonF(); // key ranges have no overlap
16529 croppedData->remove(0, lowBound);
16530 // set lowest point of cropped data to fit exactly key position of first
16531 // static data point via linear interpolation:
16532 if (croppedData->size() < 2)
16533 return QPolygonF(); // need at least two points for interpolation
16534 double slope;
16535 if (croppedData->at(1).x() - croppedData->at(0).x() != 0)
16536 slope = (croppedData->at(1).y() - croppedData->at(0).y()) /
16537 (croppedData->at(1).x() - croppedData->at(0).x());
16538 else
16539 slope = 0;
16540 (*croppedData)[0].setY(
16541 croppedData->at(0).y() +
16542 slope * (staticData->first().x() - croppedData->at(0).x()));
16543 (*croppedData)[0].setX(staticData->first().x());
16544
16545 // crop upper bound:
16546 if (staticData->last().x() >
16547 croppedData->last().x()) // other one must be cropped
16548 qSwap(staticData, croppedData);
16549 int highBound = findIndexAboveX(croppedData, staticData->last().x());
16550 if (highBound == -1) return QPolygonF(); // key ranges have no overlap
16551 croppedData->remove(highBound + 1, croppedData->size() - (highBound + 1));
16552 // set highest point of cropped data to fit exactly key position of last
16553 // static data point via linear interpolation:
16554 if (croppedData->size() < 2)
16555 return QPolygonF(); // need at least two points for interpolation
16556 int li = croppedData->size() - 1; // last index
16557 if (croppedData->at(li).x() - croppedData->at(li - 1).x() != 0)
16558 slope = (croppedData->at(li).y() - croppedData->at(li - 1).y()) /
16559 (croppedData->at(li).x() - croppedData->at(li - 1).x());
16560 else
16561 slope = 0;
16562 (*croppedData)[li].setY(
16563 croppedData->at(li - 1).y() +
16564 slope * (staticData->last().x() - croppedData->at(li - 1).x()));
16565 (*croppedData)[li].setX(staticData->last().x());
16566 } else // mKeyAxis->orientation() == Qt::Vertical
16567 {
16568 // y is key
16569 // similar to "x is key" but switched x,y. Further, lower/upper meaning is
16570 // inverted compared to x, because in pixel coordinates, y increases from
16571 // top to bottom, not bottom to top like data coordinate. if an axis range
16572 // is reversed, the data point keys will be descending. Reverse them, since
16573 // following algorithm assumes ascending keys:
16574 if (staticData->first().y() < staticData->last().y()) {
16575 int size = staticData->size();
16576 for (int i = 0; i < size / 2; ++i)
16577 qSwap((*staticData)[i], (*staticData)[size - 1 - i]);
16578 }
16579 if (croppedData->first().y() < croppedData->last().y()) {
16580 int size = croppedData->size();
16581 for (int i = 0; i < size / 2; ++i)
16582 qSwap((*croppedData)[i], (*croppedData)[size - 1 - i]);
16583 }
16584 // crop lower bound:
16585 if (staticData->first().y() >
16586 croppedData->first().y()) // other one must be cropped
16587 qSwap(staticData, croppedData);
16588 int lowBound = findIndexAboveY(croppedData, staticData->first().y());
16589 if (lowBound == -1) return QPolygonF(); // key ranges have no overlap
16590 croppedData->remove(0, lowBound);
16591 // set lowest point of cropped data to fit exactly key position of first
16592 // static data point via linear interpolation:
16593 if (croppedData->size() < 2)
16594 return QPolygonF(); // need at least two points for interpolation
16595 double slope;
16596 if (croppedData->at(1).y() - croppedData->at(0).y() !=
16597 0) // avoid division by zero in step plots
16598 slope = (croppedData->at(1).x() - croppedData->at(0).x()) /
16599 (croppedData->at(1).y() - croppedData->at(0).y());
16600 else
16601 slope = 0;
16602 (*croppedData)[0].setX(
16603 croppedData->at(0).x() +
16604 slope * (staticData->first().y() - croppedData->at(0).y()));
16605 (*croppedData)[0].setY(staticData->first().y());
16606
16607 // crop upper bound:
16608 if (staticData->last().y() <
16609 croppedData->last().y()) // other one must be cropped
16610 qSwap(staticData, croppedData);
16611 int highBound = findIndexBelowY(croppedData, staticData->last().y());
16612 if (highBound == -1) return QPolygonF(); // key ranges have no overlap
16613 croppedData->remove(highBound + 1, croppedData->size() - (highBound + 1));
16614 // set highest point of cropped data to fit exactly key position of last
16615 // static data point via linear interpolation:
16616 if (croppedData->size() < 2)
16617 return QPolygonF(); // need at least two points for interpolation
16618 int li = croppedData->size() - 1; // last index
16619 if (croppedData->at(li).y() - croppedData->at(li - 1).y() !=
16620 0) // avoid division by zero in step plots
16621 slope = (croppedData->at(li).x() - croppedData->at(li - 1).x()) /
16622 (croppedData->at(li).y() - croppedData->at(li - 1).y());
16623 else
16624 slope = 0;
16625 (*croppedData)[li].setX(
16626 croppedData->at(li - 1).x() +
16627 slope * (staticData->last().y() - croppedData->at(li - 1).y()));
16628 (*croppedData)[li].setY(staticData->last().y());
16629 }
16630
16631 // return joined:
16632 for (int i = otherData.size() - 1; i >= 0;
16633 --i) // insert reversed, otherwise the polygon will be twisted
16634 thisData << otherData.at(i);
16635 return QPolygonF(thisData);
16636 }
16637
16638 /*! \internal
16639
16640 Finds the smallest index of \a data, whose points x value is just above \a x.
16641 Assumes x values in \a data points are ordered ascending, as is the case when
16642 plotting with horizontal key axis.
16643
16644 Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
16645 */
16646 int QCPGraph::findIndexAboveX(const QVector<QPointF> *data, double x) const {
16647 for (int i = data->size() - 1; i >= 0; --i) {
16648 if (data->at(i).x() < x) {
16649 if (i < data->size() - 1)
16650 return i + 1;
16651 else
16652 return data->size() - 1;
16653 }
16654 }
16655 return -1;
16656 }
16657
16658 /*! \internal
16659
16660 Finds the highest index of \a data, whose points x value is just below \a x.
16661 Assumes x values in \a data points are ordered ascending, as is the case when
16662 plotting with horizontal key axis.
16663
16664 Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
16665 */
16666 int QCPGraph::findIndexBelowX(const QVector<QPointF> *data, double x) const {
16667 for (int i = 0; i < data->size(); ++i) {
16668 if (data->at(i).x() > x) {
16669 if (i > 0)
16670 return i - 1;
16671 else
16672 return 0;
16673 }
16674 }
16675 return -1;
16676 }
16677
16678 /*! \internal
16679
16680 Finds the smallest index of \a data, whose points y value is just above \a y.
16681 Assumes y values in \a data points are ordered descending, as is the case when
16682 plotting with vertical key axis.
16683
16684 Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
16685 */
16686 int QCPGraph::findIndexAboveY(const QVector<QPointF> *data, double y) const {
16687 for (int i = 0; i < data->size(); ++i) {
16688 if (data->at(i).y() < y) {
16689 if (i > 0)
16690 return i - 1;
16691 else
16692 return 0;
16693 }
16694 }
16695 return -1;
16696 }
16697
16698 /*! \internal
16699
16700 Calculates the (minimum) distance (in pixels) the graph's representation has
16701 from the given \a pixelPoint in pixels. This is used to determine whether the
16702 graph was clicked or not, e.g. in \ref selectTest.
16703
16704 If either the graph has no data or if the line style is \ref lsNone and the
16705 scatter style's shape is \ref QCPScatterStyle::ssNone (i.e. there is no visual
16706 representation of the graph), returns -1.0.
16707 */
16708 double QCPGraph::pointDistance(const QPointF &pixelPoint) const {
16709 if (mData->isEmpty()) return -1.0;
16710 if (mLineStyle == lsNone && mScatterStyle.isNone()) return -1.0;
16711
16712 // calculate minimum distances to graph representation:
16713 if (mLineStyle == lsNone) {
16714 // no line displayed, only calculate distance to scatter points:
16715 QVector<QCPData> scatterData;
16716 getScatterPlotData(&scatterData);
16717 if (scatterData.size() > 0) {
16718 double minDistSqr = std::numeric_limits<double>::max();
16719 for (int i = 0; i < scatterData.size(); ++i) {
16720 double currentDistSqr =
16721 QVector2D(
16722 coordsToPixels(scatterData.at(i).key, scatterData.at(i).value) -
16723 pixelPoint)
16724 .lengthSquared();
16725 if (currentDistSqr < minDistSqr) minDistSqr = currentDistSqr;
16726 }
16727 return qSqrt(minDistSqr);
16728 } else // no data available in view to calculate distance to
16729 return -1.0;
16730 } else {
16731 // line displayed, calculate distance to line segments:
16732 QVector<QPointF> lineData;
16733 getPlotData(
16734 &lineData,
16735 0); // unlike with getScatterPlotData we get pixel coordinates here
16736 if (lineData.size() >
16737 1) // at least one line segment, compare distance to line segments
16738 {
16739 double minDistSqr = std::numeric_limits<double>::max();
16740 if (mLineStyle == lsImpulse) {
16741 // impulse plot differs from other line styles in that the lineData
16742 // points are only pairwise connected:
16743 for (int i = 0; i < lineData.size() - 1; i += 2) // iterate pairs
16744 {
16745 double currentDistSqr =
16746 distSqrToLine(lineData.at(i), lineData.at(i + 1), pixelPoint);
16747 if (currentDistSqr < minDistSqr) minDistSqr = currentDistSqr;
16748 }
16749 } else {
16750 // all other line plots (line and step) connect points directly:
16751 for (int i = 0; i < lineData.size() - 1; ++i) {
16752 double currentDistSqr =
16753 distSqrToLine(lineData.at(i), lineData.at(i + 1), pixelPoint);
16754 if (currentDistSqr < minDistSqr) minDistSqr = currentDistSqr;
16755 }
16756 }
16757 return qSqrt(minDistSqr);
16758 } else if (lineData.size() >
16759 0) // only single data point, calculate distance to that point
16760 {
16761 return QVector2D(lineData.at(0) - pixelPoint).length();
16762 } else // no data available in view to calculate distance to
16763 return -1.0;
16764 }
16765 }
16766
16767 /*! \internal
16768
16769 Finds the highest index of \a data, whose points y value is just below \a y.
16770 Assumes y values in \a data points are ordered descending, as is the case when
16771 plotting with vertical key axis (since keys are ordered ascending).
16772
16773 Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
16774 */
16775 int QCPGraph::findIndexBelowY(const QVector<QPointF> *data, double y) const {
16776 for (int i = data->size() - 1; i >= 0; --i) {
16777 if (data->at(i).y() > y) {
16778 if (i < data->size() - 1)
16779 return i + 1;
16780 else
16781 return data->size() - 1;
16782 }
16783 }
16784 return -1;
16785 }
16786
16787 /* inherits documentation from base class */
16788 QCPRange QCPGraph::getKeyRange(bool &foundRange,
16789 SignDomain inSignDomain) const {
16790 // just call the specialized version which takes an additional argument
16791 // whether error bars should also be taken into consideration for range
16792 // calculation. We set this to true here.
16793 return getKeyRange(foundRange, inSignDomain, true);
16794 }
16795
16796 /* inherits documentation from base class */
16797 QCPRange QCPGraph::getValueRange(bool &foundRange,
16798 SignDomain inSignDomain) const {
16799 // just call the specialized version which takes an additional argument
16800 // whether error bars should also be taken into consideration for range
16801 // calculation. We set this to true here.
16802 return getValueRange(foundRange, inSignDomain, true);
16803 }
16804
16805 /*! \overload
16806
16807 Allows to specify whether the error bars should be included in the range
16808 calculation.
16809
16810 \see getKeyRange(bool &foundRange, SignDomain inSignDomain)
16811 */
16812 QCPRange QCPGraph::getKeyRange(bool &foundRange, SignDomain inSignDomain,
16813 bool includeErrors) const {
16814 QCPRange range;
16815 bool haveLower = false;
16816 bool haveUpper = false;
16817
16818 double current, currentErrorMinus, currentErrorPlus;
16819
16820 if (inSignDomain == sdBoth) // range may be anywhere
16821 {
16822 QCPDataMap::const_iterator it = mData->constBegin();
16823 while (it != mData->constEnd()) {
16824 if (!qIsNaN(it.value().value)) {
16825 current = it.value().key;
16826 currentErrorMinus = (includeErrors ? it.value().keyErrorMinus : 0);
16827 currentErrorPlus = (includeErrors ? it.value().keyErrorPlus : 0);
16828 if (current - currentErrorMinus < range.lower || !haveLower) {
16829 range.lower = current - currentErrorMinus;
16830 haveLower = true;
16831 }
16832 if (current + currentErrorPlus > range.upper || !haveUpper) {
16833 range.upper = current + currentErrorPlus;
16834 haveUpper = true;
16835 }
16836 }
16837 ++it;
16838 }
16839 } else if (inSignDomain ==
16840 sdNegative) // range may only be in the negative sign domain
16841 {
16842 QCPDataMap::const_iterator it = mData->constBegin();
16843 while (it != mData->constEnd()) {
16844 if (!qIsNaN(it.value().value)) {
16845 current = it.value().key;
16846 currentErrorMinus = (includeErrors ? it.value().keyErrorMinus : 0);
16847 currentErrorPlus = (includeErrors ? it.value().keyErrorPlus : 0);
16848 if ((current - currentErrorMinus < range.lower || !haveLower) &&
16849 current - currentErrorMinus < 0) {
16850 range.lower = current - currentErrorMinus;
16851 haveLower = true;
16852 }
16853 if ((current + currentErrorPlus > range.upper || !haveUpper) &&
16854 current + currentErrorPlus < 0) {
16855 range.upper = current + currentErrorPlus;
16856 haveUpper = true;
16857 }
16858 if (includeErrors) // in case point is in valid sign domain but
16859 // errobars stretch beyond it, we still want to geht
16860 // that point.
16861 {
16862 if ((current < range.lower || !haveLower) && current < 0) {
16863 range.lower = current;
16864 haveLower = true;
16865 }
16866 if ((current > range.upper || !haveUpper) && current < 0) {
16867 range.upper = current;
16868 haveUpper = true;
16869 }
16870 }
16871 }
16872 ++it;
16873 }
16874 } else if (inSignDomain ==
16875 sdPositive) // range may only be in the positive sign domain
16876 {
16877 QCPDataMap::const_iterator it = mData->constBegin();
16878 while (it != mData->constEnd()) {
16879 if (!qIsNaN(it.value().value)) {
16880 current = it.value().key;
16881 currentErrorMinus = (includeErrors ? it.value().keyErrorMinus : 0);
16882 currentErrorPlus = (includeErrors ? it.value().keyErrorPlus : 0);
16883 if ((current - currentErrorMinus < range.lower || !haveLower) &&
16884 current - currentErrorMinus > 0) {
16885 range.lower = current - currentErrorMinus;
16886 haveLower = true;
16887 }
16888 if ((current + currentErrorPlus > range.upper || !haveUpper) &&
16889 current + currentErrorPlus > 0) {
16890 range.upper = current + currentErrorPlus;
16891 haveUpper = true;
16892 }
16893 if (includeErrors) // in case point is in valid sign domain but
16894 // errobars stretch beyond it, we still want to get
16895 // that point.
16896 {
16897 if ((current < range.lower || !haveLower) && current > 0) {
16898 range.lower = current;
16899 haveLower = true;
16900 }
16901 if ((current > range.upper || !haveUpper) && current > 0) {
16902 range.upper = current;
16903 haveUpper = true;
16904 }
16905 }
16906 }
16907 ++it;
16908 }
16909 }
16910
16911 foundRange = haveLower && haveUpper;
16912 return range;
16913 }
16914
16915 /*! \overload
16916
16917 Allows to specify whether the error bars should be included in the range
16918 calculation.
16919
16920 \see getValueRange(bool &foundRange, SignDomain inSignDomain)
16921 */
16922 QCPRange QCPGraph::getValueRange(bool &foundRange, SignDomain inSignDomain,
16923 bool includeErrors) const {
16924 QCPRange range;
16925 bool haveLower = false;
16926 bool haveUpper = false;
16927
16928 double current, currentErrorMinus, currentErrorPlus;
16929
16930 if (inSignDomain == sdBoth) // range may be anywhere
16931 {
16932 QCPDataMap::const_iterator it = mData->constBegin();
16933 while (it != mData->constEnd()) {
16934 current = it.value().value;
16935 if (!qIsNaN(current)) {
16936 currentErrorMinus = (includeErrors ? it.value().valueErrorMinus : 0);
16937 currentErrorPlus = (includeErrors ? it.value().valueErrorPlus : 0);
16938 if (current - currentErrorMinus < range.lower || !haveLower) {
16939 range.lower = current - currentErrorMinus;
16940 haveLower = true;
16941 }
16942 if (current + currentErrorPlus > range.upper || !haveUpper) {
16943 range.upper = current + currentErrorPlus;
16944 haveUpper = true;
16945 }
16946 }
16947 ++it;
16948 }
16949 } else if (inSignDomain ==
16950 sdNegative) // range may only be in the negative sign domain
16951 {
16952 QCPDataMap::const_iterator it = mData->constBegin();
16953 while (it != mData->constEnd()) {
16954 current = it.value().value;
16955 if (!qIsNaN(current)) {
16956 currentErrorMinus = (includeErrors ? it.value().valueErrorMinus : 0);
16957 currentErrorPlus = (includeErrors ? it.value().valueErrorPlus : 0);
16958 if ((current - currentErrorMinus < range.lower || !haveLower) &&
16959 current - currentErrorMinus < 0) {
16960 range.lower = current - currentErrorMinus;
16961 haveLower = true;
16962 }
16963 if ((current + currentErrorPlus > range.upper || !haveUpper) &&
16964 current + currentErrorPlus < 0) {
16965 range.upper = current + currentErrorPlus;
16966 haveUpper = true;
16967 }
16968 if (includeErrors) // in case point is in valid sign domain but
16969 // errobars stretch beyond it, we still want to get
16970 // that point.
16971 {
16972 if ((current < range.lower || !haveLower) && current < 0) {
16973 range.lower = current;
16974 haveLower = true;
16975 }
16976 if ((current > range.upper || !haveUpper) && current < 0) {
16977 range.upper = current;
16978 haveUpper = true;
16979 }
16980 }
16981 }
16982 ++it;
16983 }
16984 } else if (inSignDomain ==
16985 sdPositive) // range may only be in the positive sign domain
16986 {
16987 QCPDataMap::const_iterator it = mData->constBegin();
16988 while (it != mData->constEnd()) {
16989 current = it.value().value;
16990 if (!qIsNaN(current)) {
16991 currentErrorMinus = (includeErrors ? it.value().valueErrorMinus : 0);
16992 currentErrorPlus = (includeErrors ? it.value().valueErrorPlus : 0);
16993 if ((current - currentErrorMinus < range.lower || !haveLower) &&
16994 current - currentErrorMinus > 0) {
16995 range.lower = current - currentErrorMinus;
16996 haveLower = true;
16997 }
16998 if ((current + currentErrorPlus > range.upper || !haveUpper) &&
16999 current + currentErrorPlus > 0) {
17000 range.upper = current + currentErrorPlus;
17001 haveUpper = true;
17002 }
17003 if (includeErrors) // in case point is in valid sign domain but
17004 // errobars stretch beyond it, we still want to geht
17005 // that point.
17006 {
17007 if ((current < range.lower || !haveLower) && current > 0) {
17008 range.lower = current;
17009 haveLower = true;
17010 }
17011 if ((current > range.upper || !haveUpper) && current > 0) {
17012 range.upper = current;
17013 haveUpper = true;
17014 }
17015 }
17016 }
17017 ++it;
17018 }
17019 }
17020
17021 foundRange = haveLower && haveUpper;
17022 return range;
17023 }
17024
17025 ////////////////////////////////////////////////////////////////////////////////////////////////////
17026 //////////////////// QCPCurveData
17027 ////////////////////////////////////////////////////////////////////////////////////////////////////
17028
17029 /*! \class QCPCurveData
17030 \brief Holds the data of one single data point for QCPCurve.
17031
17032 The container for storing multiple data points is \ref QCPCurveDataMap.
17033
17034 The stored data is:
17035 \li \a t: the free parameter of the curve at this curve point (cp. the
17036 mathematical vector <em>(x(t), y(t))</em>) \li \a key: coordinate on the key
17037 axis of this curve point \li \a value: coordinate on the value axis of this
17038 curve point
17039
17040 \see QCPCurveDataMap
17041 */
17042
17043 /*!
17044 Constructs a curve data point with t, key and value set to zero.
17045 */
17046 QCPCurveData::QCPCurveData() : t(0), key(0), value(0) {}
17047
17048 /*!
17049 Constructs a curve data point with the specified \a t, \a key and \a value.
17050 */
17051 QCPCurveData::QCPCurveData(double t, double key, double value)
17052 : t(t), key(key), value(value) {}
17053
17054 ////////////////////////////////////////////////////////////////////////////////////////////////////
17055 //////////////////// QCPCurve
17056 ////////////////////////////////////////////////////////////////////////////////////////////////////
17057
17058 /*! \class QCPCurve
17059 \brief A plottable representing a parametric curve in a plot.
17060
17061 \image html QCPCurve.png
17062
17063 Unlike QCPGraph, plottables of this type may have multiple points with the
17064 same key coordinate, so their visual representation can have \a loops. This is
17065 realized by introducing a third coordinate \a t, which defines the order of
17066 the points described by the other two coordinates \a x and \a y.
17067
17068 To plot data, assign it with the \ref setData or \ref addData functions.
17069
17070 Gaps in the curve can be created by adding data points with NaN as key and
17071 value
17072 (<tt>qQNaN()</tt> or <tt>std::numeric_limits<double>::quiet_NaN()</tt>) in
17073 between the two data points that shall be separated.
17074
17075 \section appearance Changing the appearance
17076
17077 The appearance of the curve is determined by the pen and the brush (\ref
17078 setPen, \ref setBrush). \section usage Usage
17079
17080 Like all data representing objects in QCustomPlot, the QCPCurve is a plottable
17081 (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
17082 (QCustomPlot::plottable, QCustomPlot::addPlottable,
17083 QCustomPlot::removePlottable, etc.)
17084
17085 Usually, you first create an instance and add it to the customPlot:
17086 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-creation-1
17087 and then modify the properties of the newly created plottable, e.g.:
17088 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-creation-2
17089 */
17090
17091 /*!
17092 Constructs a curve which uses \a keyAxis as its key axis ("x") and \a
17093 valueAxis as its value axis ("y"). \a keyAxis and \a valueAxis must reside in
17094 the same QCustomPlot instance and not have the same orientation. If either of
17095 these restrictions is violated, a corresponding message is printed to the
17096 debug output (qDebug), the construction is not aborted, though.
17097
17098 The constructed QCPCurve can be added to the plot with
17099 QCustomPlot::addPlottable, QCustomPlot then takes ownership of the graph.
17100 */
17101 QCPCurve::QCPCurve(QCPAxis *keyAxis, QCPAxis *valueAxis)
17102 : QCPAbstractPlottable(keyAxis, valueAxis) {
17103 mData = new QCPCurveDataMap;
17104 mPen.setColor(Qt::blue);
17105 mPen.setStyle(Qt::SolidLine);
17106 mBrush.setColor(Qt::blue);
17107 mBrush.setStyle(Qt::NoBrush);
17108 mSelectedPen = mPen;
17109 mSelectedPen.setWidthF(2.5);
17110 mSelectedPen.setColor(QColor(80, 80, 255)); // lighter than Qt::blue of mPen
17111 mSelectedBrush = mBrush;
17112
17113 setScatterStyle(QCPScatterStyle());
17114 setLineStyle(lsLine);
17115 }
17116
17117 QCPCurve::~QCPCurve() { delete mData; }
17118
17119 /*!
17120 Replaces the current data with the provided \a data.
17121
17122 If \a copy is set to true, data points in \a data will only be copied. if
17123 false, the plottable takes ownership of the passed data and replaces the
17124 internal data pointer with it. This is significantly faster than copying for
17125 large datasets.
17126 */
17127 void QCPCurve::setData(QCPCurveDataMap *data, bool copy) {
17128 if (mData == data) {
17129 qDebug() << Q_FUNC_INFO
17130 << "The data pointer is already in (and owned by) this plottable"
17131 << reinterpret_cast<quintptr>(data);
17132 return;
17133 }
17134 if (copy) {
17135 *mData = *data;
17136 } else {
17137 delete mData;
17138 mData = data;
17139 }
17140 }
17141
17142 /*! \overload
17143
17144 Replaces the current data with the provided points in \a t, \a key and \a
17145 value tuples. The provided vectors should have equal length. Else, the number
17146 of added points will be the size of the smallest vector.
17147 */
17148 void QCPCurve::setData(const QVector<double> &t, const QVector<double> &key,
17149 const QVector<double> &value) {
17150 mData->clear();
17151 int n = t.size();
17152 n = qMin(n, key.size());
17153 n = qMin(n, value.size());
17154 QCPCurveData newData;
17155 for (int i = 0; i < n; ++i) {
17156 newData.t = t[i];
17157 newData.key = key[i];
17158 newData.value = value[i];
17159 mData->insertMulti(newData.t, newData);
17160 }
17161 }
17162
17163 /*! \overload
17164
17165 Replaces the current data with the provided \a key and \a value pairs. The t
17166 parameter of each data point will be set to the integer index of the
17167 respective key/value pair.
17168 */
17169 void QCPCurve::setData(const QVector<double> &key,
17170 const QVector<double> &value) {
17171 mData->clear();
17172 int n = key.size();
17173 n = qMin(n, value.size());
17174 QCPCurveData newData;
17175 for (int i = 0; i < n; ++i) {
17176 newData.t =
17177 i; // no t vector given, so we assign t the index of the key/value pair
17178 newData.key = key[i];
17179 newData.value = value[i];
17180 mData->insertMulti(newData.t, newData);
17181 }
17182 }
17183
17184 /*!
17185 Sets the visual appearance of single data points in the plot. If set to \ref
17186 QCPScatterStyle::ssNone, no scatter points are drawn (e.g. for line-only plots
17187 with appropriate line style).
17188
17189 \see QCPScatterStyle, setLineStyle
17190 */
17191 void QCPCurve::setScatterStyle(const QCPScatterStyle &style) {
17192 mScatterStyle = style;
17193 }
17194
17195 /*!
17196 Sets how the single data points are connected in the plot or how they are
17197 represented visually apart from the scatter symbol. For scatter-only plots,
17198 set \a style to \ref lsNone and \ref setScatterStyle to the desired scatter
17199 style.
17200
17201 \see setScatterStyle
17202 */
17203 void QCPCurve::setLineStyle(QCPCurve::LineStyle style) { mLineStyle = style; }
17204
17205 /*!
17206 Adds the provided data points in \a dataMap to the current data.
17207 \see removeData
17208 */
17209 void QCPCurve::addData(const QCPCurveDataMap &dataMap) {
17210 mData->unite(dataMap);
17211 }
17212
17213 /*! \overload
17214 Adds the provided single data point in \a data to the current data.
17215 \see removeData
17216 */
17217 void QCPCurve::addData(const QCPCurveData &data) {
17218 mData->insertMulti(data.t, data);
17219 }
17220
17221 /*! \overload
17222 Adds the provided single data point as \a t, \a key and \a value tuple to the
17223 current data \see removeData
17224 */
17225 void QCPCurve::addData(double t, double key, double value) {
17226 QCPCurveData newData;
17227 newData.t = t;
17228 newData.key = key;
17229 newData.value = value;
17230 mData->insertMulti(newData.t, newData);
17231 }
17232
17233 /*! \overload
17234
17235 Adds the provided single data point as \a key and \a value pair to the current
17236 data The t parameter of the data point is set to the t of the last data point
17237 plus 1. If there is no last data point, t will be set to 0.
17238
17239 \see removeData
17240 */
17241 void QCPCurve::addData(double key, double value) {
17242 QCPCurveData newData;
17243 if (!mData->isEmpty())
17244 newData.t = (mData->constEnd() - 1).key() + 1;
17245 else
17246 newData.t = 0;
17247 newData.key = key;
17248 newData.value = value;
17249 mData->insertMulti(newData.t, newData);
17250 }
17251
17252 /*! \overload
17253 Adds the provided data points as \a t, \a key and \a value tuples to the
17254 current data. \see removeData
17255 */
17256 void QCPCurve::addData(const QVector<double> &ts, const QVector<double> &keys,
17257 const QVector<double> &values) {
17258 int n = ts.size();
17259 n = qMin(n, keys.size());
17260 n = qMin(n, values.size());
17261 QCPCurveData newData;
17262 for (int i = 0; i < n; ++i) {
17263 newData.t = ts[i];
17264 newData.key = keys[i];
17265 newData.value = values[i];
17266 mData->insertMulti(newData.t, newData);
17267 }
17268 }
17269
17270 /*!
17271 Removes all data points with curve parameter t smaller than \a t.
17272 \see addData, clearData
17273 */
17274 void QCPCurve::removeDataBefore(double t) {
17275 QCPCurveDataMap::iterator it = mData->begin();
17276 while (it != mData->end() && it.key() < t) it = mData->erase(it);
17277 }
17278
17279 /*!
17280 Removes all data points with curve parameter t greater than \a t.
17281 \see addData, clearData
17282 */
17283 void QCPCurve::removeDataAfter(double t) {
17284 if (mData->isEmpty()) return;
17285 QCPCurveDataMap::iterator it = mData->upperBound(t);
17286 while (it != mData->end()) it = mData->erase(it);
17287 }
17288
17289 /*!
17290 Removes all data points with curve parameter t between \a fromt and \a tot. if
17291 \a fromt is greater or equal to \a tot, the function does nothing. To remove a
17292 single data point with known t, use \ref removeData(double t).
17293
17294 \see addData, clearData
17295 */
17296 void QCPCurve::removeData(double fromt, double tot) {
17297 if (fromt >= tot || mData->isEmpty()) return;
17298 QCPCurveDataMap::iterator it = mData->upperBound(fromt);
17299 QCPCurveDataMap::iterator itEnd = mData->upperBound(tot);
17300 while (it != itEnd) it = mData->erase(it);
17301 }
17302
17303 /*! \overload
17304
17305 Removes a single data point at curve parameter \a t. If the position is not
17306 known with absolute precision, consider using \ref removeData(double fromt,
17307 double tot) with a small fuzziness interval around the suspected position,
17308 depeding on the precision with which the curve parameter is known.
17309
17310 \see addData, clearData
17311 */
17312 void QCPCurve::removeData(double t) { mData->remove(t); }
17313
17314 /*!
17315 Removes all data points.
17316 \see removeData, removeDataAfter, removeDataBefore
17317 */
17318 void QCPCurve::clearData() { mData->clear(); }
17319
17320 /* inherits documentation from base class */
17321 double QCPCurve::selectTest(const QPointF &pos, bool onlySelectable,
17322 QVariant *details) const {
17323 Q_UNUSED(details)
17324 if ((onlySelectable && !mSelectable) || mData->isEmpty()) return -1;
17325 if (!mKeyAxis || !mValueAxis) {
17326 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
17327 return -1;
17328 }
17329
17330 if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
17331 return pointDistance(pos);
17332 else
17333 return -1;
17334 }
17335
17336 /* inherits documentation from base class */
17337 void QCPCurve::draw(QCPPainter *painter) {
17338 if (mData->isEmpty()) return;
17339
17340 // allocate line vector:
17341 QVector<QPointF> *lineData = new QVector<QPointF>;
17342
17343 // fill with curve data:
17344 getCurveData(lineData);
17345
17346 // check data validity if flag set:
17347 #ifdef QCUSTOMPLOT_CHECK_DATA
17348 QCPCurveDataMap::const_iterator it;
17349 for (it = mData->constBegin(); it != mData->constEnd(); ++it) {
17350 if (QCP::isInvalidData(it.value().t) ||
17351 QCP::isInvalidData(it.value().key, it.value().value))
17352 qDebug() << Q_FUNC_INFO << "Data point at" << it.key() << "invalid."
17353 << "Plottable name:" << name();
17354 }
17355 #endif
17356
17357 // draw curve fill:
17358 if (mainBrush().style() != Qt::NoBrush && mainBrush().color().alpha() != 0) {
17359 applyFillAntialiasingHint(painter);
17360 painter->setPen(Qt::NoPen);
17361 painter->setBrush(mainBrush());
17362 painter->drawPolygon(QPolygonF(*lineData));
17363 }
17364
17365 // draw curve line:
17366 if (mLineStyle != lsNone && mainPen().style() != Qt::NoPen &&
17367 mainPen().color().alpha() != 0) {
17368 applyDefaultAntialiasingHint(painter);
17369 painter->setPen(mainPen());
17370 painter->setBrush(Qt::NoBrush);
17371 // if drawing solid line and not in PDF, use much faster line drawing
17372 // instead of polyline:
17373 if (mParentPlot->plottingHints().testFlag(QCP::phFastPolylines) &&
17374 painter->pen().style() == Qt::SolidLine &&
17375 !painter->modes().testFlag(QCPPainter::pmVectorized) &&
17376 !painter->modes().testFlag(QCPPainter::pmNoCaching)) {
17377 int i = 0;
17378 bool lastIsNan = false;
17379 const int lineDataSize = lineData->size();
17380 while (i < lineDataSize &&
17381 (qIsNaN(lineData->at(i).y()) ||
17382 qIsNaN(lineData->at(i).x()))) // make sure first point is not NaN
17383 ++i;
17384 ++i; // because drawing works in 1 point retrospect
17385 while (i < lineDataSize) {
17386 if (!qIsNaN(lineData->at(i).y()) &&
17387 !qIsNaN(lineData->at(i).x())) // NaNs create a gap in the line
17388 {
17389 if (!lastIsNan)
17390 painter->drawLine(lineData->at(i - 1), lineData->at(i));
17391 else
17392 lastIsNan = false;
17393 } else
17394 lastIsNan = true;
17395 ++i;
17396 }
17397 } else {
17398 int segmentStart = 0;
17399 int i = 0;
17400 const int lineDataSize = lineData->size();
17401 while (i < lineDataSize) {
17402 if (qIsNaN(lineData->at(i).y()) ||
17403 qIsNaN(lineData->at(i).x())) // NaNs create a gap in the line
17404 {
17405 painter->drawPolyline(
17406 lineData->constData() + segmentStart,
17407 i - segmentStart); // i, because we don't want to include the
17408 // current NaN point
17409 segmentStart = i + 1;
17410 }
17411 ++i;
17412 }
17413 // draw last segment:
17414 painter->drawPolyline(lineData->constData() + segmentStart,
17415 lineDataSize - segmentStart);
17416 }
17417 }
17418
17419 // draw scatters:
17420 if (!mScatterStyle.isNone()) drawScatterPlot(painter, lineData);
17421
17422 // free allocated line data:
17423 delete lineData;
17424 }
17425
17426 /* inherits documentation from base class */
17427 void QCPCurve::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const {
17428 // draw fill:
17429 if (mBrush.style() != Qt::NoBrush) {
17430 applyFillAntialiasingHint(painter);
17431 painter->fillRect(QRectF(rect.left(), rect.top() + rect.height() / 2.0,
17432 rect.width(), rect.height() / 3.0),
17433 mBrush);
17434 }
17435 // draw line vertically centered:
17436 if (mLineStyle != lsNone) {
17437 applyDefaultAntialiasingHint(painter);
17438 painter->setPen(mPen);
17439 painter->drawLine(QLineF(
17440 rect.left(), rect.top() + rect.height() / 2.0, rect.right() + 5,
17441 rect.top() + rect.height() / 2.0)); // +5 on x2 else last segment is
17442 // missing from dashed/dotted pens
17443 }
17444 // draw scatter symbol:
17445 if (!mScatterStyle.isNone()) {
17446 applyScattersAntialiasingHint(painter);
17447 // scale scatter pixmap if it's too large to fit in legend icon rect:
17448 if (mScatterStyle.shape() == QCPScatterStyle::ssPixmap &&
17449 (mScatterStyle.pixmap().size().width() > rect.width() ||
17450 mScatterStyle.pixmap().size().height() > rect.height())) {
17451 QCPScatterStyle scaledStyle(mScatterStyle);
17452 scaledStyle.setPixmap(scaledStyle.pixmap().scaled(
17453 rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
17454 scaledStyle.applyTo(painter, mPen);
17455 scaledStyle.drawShape(painter, QRectF(rect).center());
17456 } else {
17457 mScatterStyle.applyTo(painter, mPen);
17458 mScatterStyle.drawShape(painter, QRectF(rect).center());
17459 }
17460 }
17461 }
17462
17463 /*! \internal
17464
17465 Draws scatter symbols at every data point passed in \a pointData. scatter
17466 symbols are independent of the line style and are always drawn if scatter
17467 shape is not \ref QCPScatterStyle::ssNone.
17468 */
17469 void QCPCurve::drawScatterPlot(QCPPainter *painter,
17470 const QVector<QPointF> *pointData) const {
17471 // draw scatter point symbols:
17472 applyScattersAntialiasingHint(painter);
17473 mScatterStyle.applyTo(painter, mPen);
17474 for (int i = 0; i < pointData->size(); ++i)
17475 if (!qIsNaN(pointData->at(i).x()) && !qIsNaN(pointData->at(i).y()))
17476 mScatterStyle.drawShape(painter, pointData->at(i));
17477 }
17478
17479 /*! \internal
17480
17481 called by QCPCurve::draw to generate a point vector (in pixel coordinates)
17482 which represents the line of the curve.
17483
17484 Line segments that aren't visible in the current axis rect are handled in an
17485 optimized way. They are projected onto a rectangle slightly larger than the
17486 visible axis rect and simplified regarding point count. The algorithm makes
17487 sure to preserve appearance of lines and fills inside the visible axis rect by
17488 generating new temporary points on the outer rect if necessary.
17489
17490 Methods that are also involved in the algorithm are: \ref getRegion, \ref
17491 getOptimizedPoint, \ref getOptimizedCornerPoints \ref mayTraverse, \ref
17492 getTraverse, \ref getTraverseCornerPoints.
17493 */
17494 void QCPCurve::getCurveData(QVector<QPointF> *lineData) const {
17495 QCPAxis *keyAxis = mKeyAxis.data();
17496 QCPAxis *valueAxis = mValueAxis.data();
17497 if (!keyAxis || !valueAxis) {
17498 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
17499 return;
17500 }
17501
17502 // add margins to rect to compensate for stroke width
17503 double strokeMargin =
17504 qMax(qreal(1.0),
17505 qreal(mainPen().widthF() * 0.75)); // stroke radius + 50% safety
17506 if (!mScatterStyle.isNone())
17507 strokeMargin = qMax(strokeMargin, mScatterStyle.size());
17508 double rectLeft = keyAxis->pixelToCoord(
17509 keyAxis->coordToPixel(keyAxis->range().lower) -
17510 strokeMargin *
17511 ((keyAxis->orientation() == Qt::Vertical) != keyAxis->rangeReversed()
17512 ? -1
17513 : 1));
17514 double rectRight = keyAxis->pixelToCoord(
17515 keyAxis->coordToPixel(keyAxis->range().upper) +
17516 strokeMargin *
17517 ((keyAxis->orientation() == Qt::Vertical) != keyAxis->rangeReversed()
17518 ? -1
17519 : 1));
17520 double rectBottom = valueAxis->pixelToCoord(
17521 valueAxis->coordToPixel(valueAxis->range().lower) +
17522 strokeMargin * ((valueAxis->orientation() == Qt::Horizontal) !=
17523 valueAxis->rangeReversed()
17524 ? -1
17525 : 1));
17526 double rectTop = valueAxis->pixelToCoord(
17527 valueAxis->coordToPixel(valueAxis->range().upper) -
17528 strokeMargin * ((valueAxis->orientation() == Qt::Horizontal) !=
17529 valueAxis->rangeReversed()
17530 ? -1
17531 : 1));
17532 int currentRegion;
17533 QCPCurveDataMap::const_iterator it = mData->constBegin();
17534 QCPCurveDataMap::const_iterator prevIt = mData->constEnd() - 1;
17535 int prevRegion = getRegion(prevIt.value().key, prevIt.value().value, rectLeft,
17536 rectTop, rectRight, rectBottom);
17537 QVector<QPointF>
17538 trailingPoints; // points that must be applied after all other points
17539 // (are generated only when handling first point to get
17540 // virtual segment between last and first point right)
17541 while (it != mData->constEnd()) {
17542 currentRegion = getRegion(it.value().key, it.value().value, rectLeft,
17543 rectTop, rectRight, rectBottom);
17544 if (currentRegion !=
17545 prevRegion) // changed region, possibly need to add some optimized edge
17546 // points or original points if entering R
17547 {
17548 if (currentRegion !=
17549 5) // segment doesn't end in R, so it's a candidate for removal
17550 {
17551 QPointF crossA, crossB;
17552 if (prevRegion ==
17553 5) // we're coming from R, so add this point optimized
17554 {
17555 lineData->append(
17556 getOptimizedPoint(currentRegion, it.value().key, it.value().value,
17557 prevIt.value().key, prevIt.value().value,
17558 rectLeft, rectTop, rectRight, rectBottom));
17559 // in the situations 5->1/7/9/3 the segment may leave R and directly
17560 // cross through two outer regions. In these cases we need to add an
17561 // additional corner point
17562 *lineData << getOptimizedCornerPoints(
17563 prevRegion, currentRegion, prevIt.value().key,
17564 prevIt.value().value, it.value().key, it.value().value, rectLeft,
17565 rectTop, rectRight, rectBottom);
17566 } else if (mayTraverse(prevRegion, currentRegion) &&
17567 getTraverse(prevIt.value().key, prevIt.value().value,
17568 it.value().key, it.value().value, rectLeft,
17569 rectTop, rectRight, rectBottom, crossA,
17570 crossB)) {
17571 // add the two cross points optimized if segment crosses R and if
17572 // segment isn't virtual zeroth segment between last and first curve
17573 // point:
17574 QVector<QPointF> beforeTraverseCornerPoints,
17575 afterTraverseCornerPoints;
17576 getTraverseCornerPoints(prevRegion, currentRegion, rectLeft, rectTop,
17577 rectRight, rectBottom,
17578 beforeTraverseCornerPoints,
17579 afterTraverseCornerPoints);
17580 if (it != mData->constBegin()) {
17581 *lineData << beforeTraverseCornerPoints;
17582 lineData->append(crossA);
17583 lineData->append(crossB);
17584 *lineData << afterTraverseCornerPoints;
17585 } else {
17586 lineData->append(crossB);
17587 *lineData << afterTraverseCornerPoints;
17588 trailingPoints << beforeTraverseCornerPoints << crossA;
17589 }
17590 } else // doesn't cross R, line is just moving around in outside
17591 // regions, so only need to add optimized point(s) at the
17592 // boundary corner(s)
17593 {
17594 *lineData << getOptimizedCornerPoints(
17595 prevRegion, currentRegion, prevIt.value().key,
17596 prevIt.value().value, it.value().key, it.value().value, rectLeft,
17597 rectTop, rectRight, rectBottom);
17598 }
17599 } else // segment does end in R, so we add previous point optimized and
17600 // this point at original position
17601 {
17602 if (it ==
17603 mData->constBegin()) // it is first point in curve and prevIt is
17604 // last one. So save optimized point for
17605 // adding it to the lineData in the end
17606 trailingPoints << getOptimizedPoint(
17607 prevRegion, prevIt.value().key, prevIt.value().value,
17608 it.value().key, it.value().value, rectLeft, rectTop, rectRight,
17609 rectBottom);
17610 else
17611 lineData->append(getOptimizedPoint(
17612 prevRegion, prevIt.value().key, prevIt.value().value,
17613 it.value().key, it.value().value, rectLeft, rectTop, rectRight,
17614 rectBottom));
17615 lineData->append(coordsToPixels(it.value().key, it.value().value));
17616 }
17617 } else // region didn't change
17618 {
17619 if (currentRegion == 5) // still in R, keep adding original points
17620 {
17621 lineData->append(coordsToPixels(it.value().key, it.value().value));
17622 } else // still outside R, no need to add anything
17623 {
17624 // see how this is not doing anything? That's the main optimization...
17625 }
17626 }
17627 prevIt = it;
17628 prevRegion = currentRegion;
17629 ++it;
17630 }
17631 *lineData << trailingPoints;
17632 }
17633
17634 /*! \internal
17635
17636 This function is part of the curve optimization algorithm of \ref
17637 getCurveData.
17638
17639 It returns the region of the given point (\a x, \a y) with respect to a
17640 rectangle defined by \a rectLeft, \a rectTop, \a rectRight, and \a rectBottom.
17641
17642 The regions are enumerated from top to bottom and left to right:
17643
17644 <table style="width:10em; text-align:center">
17645 <tr><td>1</td><td>4</td><td>7</td></tr>
17646 <tr><td>2</td><td style="border:1px solid black">5</td><td>8</td></tr>
17647 <tr><td>3</td><td>6</td><td>9</td></tr>
17648 </table>
17649
17650 With the rectangle being region 5, and the outer regions extending infinitely
17651 outwards. In the curve optimization algorithm, region 5 is considered to be
17652 the visible portion of the plot.
17653 */
17654 int QCPCurve::getRegion(double x, double y, double rectLeft, double rectTop,
17655 double rectRight, double rectBottom) const {
17656 if (x < rectLeft) // region 123
17657 {
17658 if (y > rectTop)
17659 return 1;
17660 else if (y < rectBottom)
17661 return 3;
17662 else
17663 return 2;
17664 } else if (x > rectRight) // region 789
17665 {
17666 if (y > rectTop)
17667 return 7;
17668 else if (y < rectBottom)
17669 return 9;
17670 else
17671 return 8;
17672 } else // region 456
17673 {
17674 if (y > rectTop)
17675 return 4;
17676 else if (y < rectBottom)
17677 return 6;
17678 else
17679 return 5;
17680 }
17681 }
17682
17683 /*! \internal
17684
17685 This function is part of the curve optimization algorithm of \ref
17686 getCurveData.
17687
17688 This method is used in case the current segment passes from inside the visible
17689 rect (region 5, see \ref getRegion) to any of the outer regions (\a
17690 otherRegion). The current segment is given by the line connecting (\a key, \a
17691 value) with (\a otherKey, \a otherValue).
17692
17693 It returns the intersection point of the segment with the border of region 5.
17694
17695 For this function it doesn't matter whether (\a key, \a value) is the point
17696 inside region 5 or whether it's (\a otherKey, \a otherValue), i.e. whether the
17697 segment is coming from region 5 or leaving it. It is important though that \a
17698 otherRegion correctly identifies the other region not equal to 5.
17699 */
17700 QPointF QCPCurve::getOptimizedPoint(int otherRegion, double otherKey,
17701 double otherValue, double key, double value,
17702 double rectLeft, double rectTop,
17703 double rectRight, double rectBottom) const {
17704 double intersectKey = rectLeft; // initial value is just fail-safe
17705 double intersectValue = rectTop; // initial value is just fail-safe
17706 switch (otherRegion) {
17707 case 1: // top and left edge
17708 {
17709 intersectValue = rectTop;
17710 intersectKey = otherKey + (key - otherKey) / (value - otherValue) *
17711 (intersectValue - otherValue);
17712 if (intersectKey < rectLeft ||
17713 intersectKey >
17714 rectRight) // doesn't intersect, so must intersect other:
17715 {
17716 intersectKey = rectLeft;
17717 intersectValue = otherValue + (value - otherValue) / (key - otherKey) *
17718 (intersectKey - otherKey);
17719 }
17720 break;
17721 }
17722 case 2: // left edge
17723 {
17724 intersectKey = rectLeft;
17725 intersectValue = otherValue + (value - otherValue) / (key - otherKey) *
17726 (intersectKey - otherKey);
17727 break;
17728 }
17729 case 3: // bottom and left edge
17730 {
17731 intersectValue = rectBottom;
17732 intersectKey = otherKey + (key - otherKey) / (value - otherValue) *
17733 (intersectValue - otherValue);
17734 if (intersectKey < rectLeft ||
17735 intersectKey >
17736 rectRight) // doesn't intersect, so must intersect other:
17737 {
17738 intersectKey = rectLeft;
17739 intersectValue = otherValue + (value - otherValue) / (key - otherKey) *
17740 (intersectKey - otherKey);
17741 }
17742 break;
17743 }
17744 case 4: // top edge
17745 {
17746 intersectValue = rectTop;
17747 intersectKey = otherKey + (key - otherKey) / (value - otherValue) *
17748 (intersectValue - otherValue);
17749 break;
17750 }
17751 case 5: {
17752 break; // case 5 shouldn't happen for this function but we add it anyway
17753 // to prevent potential discontinuity in branch table
17754 }
17755 case 6: // bottom edge
17756 {
17757 intersectValue = rectBottom;
17758 intersectKey = otherKey + (key - otherKey) / (value - otherValue) *
17759 (intersectValue - otherValue);
17760 break;
17761 }
17762 case 7: // top and right edge
17763 {
17764 intersectValue = rectTop;
17765 intersectKey = otherKey + (key - otherKey) / (value - otherValue) *
17766 (intersectValue - otherValue);
17767 if (intersectKey < rectLeft ||
17768 intersectKey >
17769 rectRight) // doesn't intersect, so must intersect other:
17770 {
17771 intersectKey = rectRight;
17772 intersectValue = otherValue + (value - otherValue) / (key - otherKey) *
17773 (intersectKey - otherKey);
17774 }
17775 break;
17776 }
17777 case 8: // right edge
17778 {
17779 intersectKey = rectRight;
17780 intersectValue = otherValue + (value - otherValue) / (key - otherKey) *
17781 (intersectKey - otherKey);
17782 break;
17783 }
17784 case 9: // bottom and right edge
17785 {
17786 intersectValue = rectBottom;
17787 intersectKey = otherKey + (key - otherKey) / (value - otherValue) *
17788 (intersectValue - otherValue);
17789 if (intersectKey < rectLeft ||
17790 intersectKey >
17791 rectRight) // doesn't intersect, so must intersect other:
17792 {
17793 intersectKey = rectRight;
17794 intersectValue = otherValue + (value - otherValue) / (key - otherKey) *
17795 (intersectKey - otherKey);
17796 }
17797 break;
17798 }
17799 }
17800 return coordsToPixels(intersectKey, intersectValue);
17801 }
17802
17803 /*! \internal
17804
17805 This function is part of the curve optimization algorithm of \ref
17806 getCurveData.
17807
17808 In situations where a single segment skips over multiple regions it might
17809 become necessary to add extra points at the corners of region 5 (see \ref
17810 getRegion) such that the optimized segment doesn't unintentionally cut through
17811 the visible area of the axis rect and create plot artifacts. This method
17812 provides these points that must be added, assuming the original segment
17813 doesn't start, end, or traverse region 5. (Corner points where region 5 is
17814 traversed are calculated by \ref getTraverseCornerPoints.)
17815
17816 For example, consider a segment which directly goes from region 4 to 2 but
17817 originally is far out to the top left such that it doesn't cross region 5.
17818 Naively optimizing these points by projecting them on the top and left borders
17819 of region 5 will create a segment that surely crosses 5, creating a visual
17820 artifact in the plot. This method prevents this by providing extra points at
17821 the top left corner, making the optimized curve correctly pass from region 4
17822 to 1 to 2 without traversing 5.
17823 */
17824 QVector<QPointF> QCPCurve::getOptimizedCornerPoints(
17825 int prevRegion, int currentRegion, double prevKey, double prevValue,
17826 double key, double value, double rectLeft, double rectTop, double rectRight,
17827 double rectBottom) const {
17828 QVector<QPointF> result;
17829 switch (prevRegion) {
17830 case 1: {
17831 switch (currentRegion) {
17832 case 2: {
17833 result << coordsToPixels(rectLeft, rectTop);
17834 break;
17835 }
17836 case 4: {
17837 result << coordsToPixels(rectLeft, rectTop);
17838 break;
17839 }
17840 case 3: {
17841 result << coordsToPixels(rectLeft, rectTop)
17842 << coordsToPixels(rectLeft, rectBottom);
17843 break;
17844 }
17845 case 7: {
17846 result << coordsToPixels(rectLeft, rectTop)
17847 << coordsToPixels(rectRight, rectTop);
17848 break;
17849 }
17850 case 6: {
17851 result << coordsToPixels(rectLeft, rectTop)
17852 << coordsToPixels(rectLeft, rectBottom);
17853 result.append(result.last());
17854 break;
17855 }
17856 case 8: {
17857 result << coordsToPixels(rectLeft, rectTop)
17858 << coordsToPixels(rectRight, rectTop);
17859 result.append(result.last());
17860 break;
17861 }
17862 case 9: { // in this case we need another distinction of cases: segment
17863 // may pass below or above rect, requiring either bottom
17864 // right or top left corner points
17865 if ((value - prevValue) / (key - prevKey) * (rectLeft - key) + value <
17866 rectBottom) // segment passes below R
17867 {
17868 result << coordsToPixels(rectLeft, rectTop)
17869 << coordsToPixels(rectLeft, rectBottom);
17870 result.append(result.last());
17871 result << coordsToPixels(rectRight, rectBottom);
17872 } else {
17873 result << coordsToPixels(rectLeft, rectTop)
17874 << coordsToPixels(rectRight, rectTop);
17875 result.append(result.last());
17876 result << coordsToPixels(rectRight, rectBottom);
17877 }
17878 break;
17879 }
17880 }
17881 break;
17882 }
17883 case 2: {
17884 switch (currentRegion) {
17885 case 1: {
17886 result << coordsToPixels(rectLeft, rectTop);
17887 break;
17888 }
17889 case 3: {
17890 result << coordsToPixels(rectLeft, rectBottom);
17891 break;
17892 }
17893 case 4: {
17894 result << coordsToPixels(rectLeft, rectTop);
17895 result.append(result.last());
17896 break;
17897 }
17898 case 6: {
17899 result << coordsToPixels(rectLeft, rectBottom);
17900 result.append(result.last());
17901 break;
17902 }
17903 case 7: {
17904 result << coordsToPixels(rectLeft, rectTop);
17905 result.append(result.last());
17906 result << coordsToPixels(rectRight, rectTop);
17907 break;
17908 }
17909 case 9: {
17910 result << coordsToPixels(rectLeft, rectBottom);
17911 result.append(result.last());
17912 result << coordsToPixels(rectRight, rectBottom);
17913 break;
17914 }
17915 }
17916 break;
17917 }
17918 case 3: {
17919 switch (currentRegion) {
17920 case 2: {
17921 result << coordsToPixels(rectLeft, rectBottom);
17922 break;
17923 }
17924 case 6: {
17925 result << coordsToPixels(rectLeft, rectBottom);
17926 break;
17927 }
17928 case 1: {
17929 result << coordsToPixels(rectLeft, rectBottom)
17930 << coordsToPixels(rectLeft, rectTop);
17931 break;
17932 }
17933 case 9: {
17934 result << coordsToPixels(rectLeft, rectBottom)
17935 << coordsToPixels(rectRight, rectBottom);
17936 break;
17937 }
17938 case 4: {
17939 result << coordsToPixels(rectLeft, rectBottom)
17940 << coordsToPixels(rectLeft, rectTop);
17941 result.append(result.last());
17942 break;
17943 }
17944 case 8: {
17945 result << coordsToPixels(rectLeft, rectBottom)
17946 << coordsToPixels(rectRight, rectBottom);
17947 result.append(result.last());
17948 break;
17949 }
17950 case 7: { // in this case we need another distinction of cases: segment
17951 // may pass below or above rect, requiring either bottom
17952 // right or top left corner points
17953 if ((value - prevValue) / (key - prevKey) * (rectRight - key) +
17954 value <
17955 rectBottom) // segment passes below R
17956 {
17957 result << coordsToPixels(rectLeft, rectBottom)
17958 << coordsToPixels(rectRight, rectBottom);
17959 result.append(result.last());
17960 result << coordsToPixels(rectRight, rectTop);
17961 } else {
17962 result << coordsToPixels(rectLeft, rectBottom)
17963 << coordsToPixels(rectLeft, rectTop);
17964 result.append(result.last());
17965 result << coordsToPixels(rectRight, rectTop);
17966 }
17967 break;
17968 }
17969 }
17970 break;
17971 }
17972 case 4: {
17973 switch (currentRegion) {
17974 case 1: {
17975 result << coordsToPixels(rectLeft, rectTop);
17976 break;
17977 }
17978 case 7: {
17979 result << coordsToPixels(rectRight, rectTop);
17980 break;
17981 }
17982 case 2: {
17983 result << coordsToPixels(rectLeft, rectTop);
17984 result.append(result.last());
17985 break;
17986 }
17987 case 8: {
17988 result << coordsToPixels(rectRight, rectTop);
17989 result.append(result.last());
17990 break;
17991 }
17992 case 3: {
17993 result << coordsToPixels(rectLeft, rectTop);
17994 result.append(result.last());
17995 result << coordsToPixels(rectLeft, rectBottom);
17996 break;
17997 }
17998 case 9: {
17999 result << coordsToPixels(rectRight, rectTop);
18000 result.append(result.last());
18001 result << coordsToPixels(rectRight, rectBottom);
18002 break;
18003 }
18004 }
18005 break;
18006 }
18007 case 5: {
18008 switch (currentRegion) {
18009 case 1: {
18010 result << coordsToPixels(rectLeft, rectTop);
18011 break;
18012 }
18013 case 7: {
18014 result << coordsToPixels(rectRight, rectTop);
18015 break;
18016 }
18017 case 9: {
18018 result << coordsToPixels(rectRight, rectBottom);
18019 break;
18020 }
18021 case 3: {
18022 result << coordsToPixels(rectLeft, rectBottom);
18023 break;
18024 }
18025 }
18026 break;
18027 }
18028 case 6: {
18029 switch (currentRegion) {
18030 case 3: {
18031 result << coordsToPixels(rectLeft, rectBottom);
18032 break;
18033 }
18034 case 9: {
18035 result << coordsToPixels(rectRight, rectBottom);
18036 break;
18037 }
18038 case 2: {
18039 result << coordsToPixels(rectLeft, rectBottom);
18040 result.append(result.last());
18041 break;
18042 }
18043 case 8: {
18044 result << coordsToPixels(rectRight, rectBottom);
18045 result.append(result.last());
18046 break;
18047 }
18048 case 1: {
18049 result << coordsToPixels(rectLeft, rectBottom);
18050 result.append(result.last());
18051 result << coordsToPixels(rectLeft, rectTop);
18052 break;
18053 }
18054 case 7: {
18055 result << coordsToPixels(rectRight, rectBottom);
18056 result.append(result.last());
18057 result << coordsToPixels(rectRight, rectTop);
18058 break;
18059 }
18060 }
18061 break;
18062 }
18063 case 7: {
18064 switch (currentRegion) {
18065 case 4: {
18066 result << coordsToPixels(rectRight, rectTop);
18067 break;
18068 }
18069 case 8: {
18070 result << coordsToPixels(rectRight, rectTop);
18071 break;
18072 }
18073 case 1: {
18074 result << coordsToPixels(rectRight, rectTop)
18075 << coordsToPixels(rectLeft, rectTop);
18076 break;
18077 }
18078 case 9: {
18079 result << coordsToPixels(rectRight, rectTop)
18080 << coordsToPixels(rectRight, rectBottom);
18081 break;
18082 }
18083 case 2: {
18084 result << coordsToPixels(rectRight, rectTop)
18085 << coordsToPixels(rectLeft, rectTop);
18086 result.append(result.last());
18087 break;
18088 }
18089 case 6: {
18090 result << coordsToPixels(rectRight, rectTop)
18091 << coordsToPixels(rectRight, rectBottom);
18092 result.append(result.last());
18093 break;
18094 }
18095 case 3: { // in this case we need another distinction of cases: segment
18096 // may pass below or above rect, requiring either bottom
18097 // right or top left corner points
18098 if ((value - prevValue) / (key - prevKey) * (rectRight - key) +
18099 value <
18100 rectBottom) // segment passes below R
18101 {
18102 result << coordsToPixels(rectRight, rectTop)
18103 << coordsToPixels(rectRight, rectBottom);
18104 result.append(result.last());
18105 result << coordsToPixels(rectLeft, rectBottom);
18106 } else {
18107 result << coordsToPixels(rectRight, rectTop)
18108 << coordsToPixels(rectLeft, rectTop);
18109 result.append(result.last());
18110 result << coordsToPixels(rectLeft, rectBottom);
18111 }
18112 break;
18113 }
18114 }
18115 break;
18116 }
18117 case 8: {
18118 switch (currentRegion) {
18119 case 7: {
18120 result << coordsToPixels(rectRight, rectTop);
18121 break;
18122 }
18123 case 9: {
18124 result << coordsToPixels(rectRight, rectBottom);
18125 break;
18126 }
18127 case 4: {
18128 result << coordsToPixels(rectRight, rectTop);
18129 result.append(result.last());
18130 break;
18131 }
18132 case 6: {
18133 result << coordsToPixels(rectRight, rectBottom);
18134 result.append(result.last());
18135 break;
18136 }
18137 case 1: {
18138 result << coordsToPixels(rectRight, rectTop);
18139 result.append(result.last());
18140 result << coordsToPixels(rectLeft, rectTop);
18141 break;
18142 }
18143 case 3: {
18144 result << coordsToPixels(rectRight, rectBottom);
18145 result.append(result.last());
18146 result << coordsToPixels(rectLeft, rectBottom);
18147 break;
18148 }
18149 }
18150 break;
18151 }
18152 case 9: {
18153 switch (currentRegion) {
18154 case 6: {
18155 result << coordsToPixels(rectRight, rectBottom);
18156 break;
18157 }
18158 case 8: {
18159 result << coordsToPixels(rectRight, rectBottom);
18160 break;
18161 }
18162 case 3: {
18163 result << coordsToPixels(rectRight, rectBottom)
18164 << coordsToPixels(rectLeft, rectBottom);
18165 break;
18166 }
18167 case 7: {
18168 result << coordsToPixels(rectRight, rectBottom)
18169 << coordsToPixels(rectRight, rectTop);
18170 break;
18171 }
18172 case 2: {
18173 result << coordsToPixels(rectRight, rectBottom)
18174 << coordsToPixels(rectLeft, rectBottom);
18175 result.append(result.last());
18176 break;
18177 }
18178 case 4: {
18179 result << coordsToPixels(rectRight, rectBottom)
18180 << coordsToPixels(rectRight, rectTop);
18181 result.append(result.last());
18182 break;
18183 }
18184 case 1: { // in this case we need another distinction of cases: segment
18185 // may pass below or above rect, requiring either bottom
18186 // right or top left corner points
18187 if ((value - prevValue) / (key - prevKey) * (rectLeft - key) + value <
18188 rectBottom) // segment passes below R
18189 {
18190 result << coordsToPixels(rectRight, rectBottom)
18191 << coordsToPixels(rectLeft, rectBottom);
18192 result.append(result.last());
18193 result << coordsToPixels(rectLeft, rectTop);
18194 } else {
18195 result << coordsToPixels(rectRight, rectBottom)
18196 << coordsToPixels(rectRight, rectTop);
18197 result.append(result.last());
18198 result << coordsToPixels(rectLeft, rectTop);
18199 }
18200 break;
18201 }
18202 }
18203 break;
18204 }
18205 }
18206 return result;
18207 }
18208
18209 /*! \internal
18210
18211 This function is part of the curve optimization algorithm of \ref
18212 getCurveData.
18213
18214 This method returns whether a segment going from \a prevRegion to \a
18215 currentRegion (see \ref getRegion) may traverse the visible region 5. This
18216 function assumes that neither \a prevRegion nor \a currentRegion is 5 itself.
18217
18218 If this method returns false, the segment for sure doesn't pass region 5. If
18219 it returns true, the segment may or may not pass region 5 and a more
18220 fine-grained calculation must be used (\ref getTraverse).
18221 */
18222 bool QCPCurve::mayTraverse(int prevRegion, int currentRegion) const {
18223 switch (prevRegion) {
18224 case 1: {
18225 switch (currentRegion) {
18226 case 4:
18227 case 7:
18228 case 2:
18229 case 3:
18230 return false;
18231 default:
18232 return true;
18233 }
18234 }
18235 case 2: {
18236 switch (currentRegion) {
18237 case 1:
18238 case 3:
18239 return false;
18240 default:
18241 return true;
18242 }
18243 }
18244 case 3: {
18245 switch (currentRegion) {
18246 case 1:
18247 case 2:
18248 case 6:
18249 case 9:
18250 return false;
18251 default:
18252 return true;
18253 }
18254 }
18255 case 4: {
18256 switch (currentRegion) {
18257 case 1:
18258 case 7:
18259 return false;
18260 default:
18261 return true;
18262 }
18263 }
18264 case 5:
18265 return false; // should never occur
18266 case 6: {
18267 switch (currentRegion) {
18268 case 3:
18269 case 9:
18270 return false;
18271 default:
18272 return true;
18273 }
18274 }
18275 case 7: {
18276 switch (currentRegion) {
18277 case 1:
18278 case 4:
18279 case 8:
18280 case 9:
18281 return false;
18282 default:
18283 return true;
18284 }
18285 }
18286 case 8: {
18287 switch (currentRegion) {
18288 case 7:
18289 case 9:
18290 return false;
18291 default:
18292 return true;
18293 }
18294 }
18295 case 9: {
18296 switch (currentRegion) {
18297 case 3:
18298 case 6:
18299 case 8:
18300 case 7:
18301 return false;
18302 default:
18303 return true;
18304 }
18305 }
18306 default:
18307 return true;
18308 }
18309 }
18310
18311 /*! \internal
18312
18313 This function is part of the curve optimization algorithm of \ref
18314 getCurveData.
18315
18316 This method assumes that the \ref mayTraverse test has returned true, so there
18317 is a chance the segment defined by (\a prevKey, \a prevValue) and (\a key, \a
18318 value) goes through the visible region 5.
18319
18320 The return value of this method indicates whether the segment actually
18321 traverses region 5 or not.
18322
18323 If the segment traverses 5, the output parameters \a crossA and \a crossB
18324 indicate the entry and exit points of region 5. They will become the optimized
18325 points for that segment.
18326 */
18327 bool QCPCurve::getTraverse(double prevKey, double prevValue, double key,
18328 double value, double rectLeft, double rectTop,
18329 double rectRight, double rectBottom, QPointF &crossA,
18330 QPointF &crossB) const {
18331 QList<QPointF>
18332 intersections; // x of QPointF corresponds to key and y to value
18333 if (qFuzzyIsNull(key - prevKey)) // line is parallel to value axis
18334 {
18335 // due to region filter in mayTraverseR(), if line is parallel to value or
18336 // key axis, R is traversed here
18337 intersections.append(QPointF(
18338 key, rectBottom)); // direction will be taken care of at end of method
18339 intersections.append(QPointF(key, rectTop));
18340 } else if (qFuzzyIsNull(value - prevValue)) // line is parallel to key axis
18341 {
18342 // due to region filter in mayTraverseR(), if line is parallel to value or
18343 // key axis, R is traversed here
18344 intersections.append(QPointF(
18345 rectLeft, value)); // direction will be taken care of at end of method
18346 intersections.append(QPointF(rectRight, value));
18347 } else // line is skewed
18348 {
18349 double gamma;
18350 double keyPerValue = (key - prevKey) / (value - prevValue);
18351 // check top of rect:
18352 gamma = prevKey + (rectTop - prevValue) * keyPerValue;
18353 if (gamma >= rectLeft && gamma <= rectRight)
18354 intersections.append(QPointF(gamma, rectTop));
18355 // check bottom of rect:
18356 gamma = prevKey + (rectBottom - prevValue) * keyPerValue;
18357 if (gamma >= rectLeft && gamma <= rectRight)
18358 intersections.append(QPointF(gamma, rectBottom));
18359 double valuePerKey = 1.0 / keyPerValue;
18360 // check left of rect:
18361 gamma = prevValue + (rectLeft - prevKey) * valuePerKey;
18362 if (gamma >= rectBottom && gamma <= rectTop)
18363 intersections.append(QPointF(rectLeft, gamma));
18364 // check right of rect:
18365 gamma = prevValue + (rectRight - prevKey) * valuePerKey;
18366 if (gamma >= rectBottom && gamma <= rectTop)
18367 intersections.append(QPointF(rectRight, gamma));
18368 }
18369
18370 // handle cases where found points isn't exactly 2:
18371 if (intersections.size() > 2) {
18372 // line probably goes through corner of rect, and we got duplicate points
18373 // there. single out the point pair with greatest distance in between:
18374 double distSqrMax = 0;
18375 QPointF pv1, pv2;
18376 for (int i = 0; i < intersections.size() - 1; ++i) {
18377 for (int k = i + 1; k < intersections.size(); ++k) {
18378 QPointF distPoint = intersections.at(i) - intersections.at(k);
18379 double distSqr =
18380 distPoint.x() * distPoint.x() + distPoint.y() + distPoint.y();
18381 if (distSqr > distSqrMax) {
18382 pv1 = intersections.at(i);
18383 pv2 = intersections.at(k);
18384 distSqrMax = distSqr;
18385 }
18386 }
18387 }
18388 intersections = QList<QPointF>() << pv1 << pv2;
18389 } else if (intersections.size() != 2) {
18390 // one or even zero points found (shouldn't happen unless line perfectly
18391 // tangent to corner), no need to draw segment
18392 return false;
18393 }
18394
18395 // possibly re-sort points so optimized point segment has same direction as
18396 // original segment:
18397 if ((key - prevKey) * (intersections.at(1).x() - intersections.at(0).x()) +
18398 (value - prevValue) *
18399 (intersections.at(1).y() - intersections.at(0).y()) <
18400 0) // scalar product of both segments < 0 -> opposite direction
18401 intersections.move(0, 1);
18402 crossA = coordsToPixels(intersections.at(0).x(), intersections.at(0).y());
18403 crossB = coordsToPixels(intersections.at(1).x(), intersections.at(1).y());
18404 return true;
18405 }
18406
18407 /*! \internal
18408
18409 This function is part of the curve optimization algorithm of \ref
18410 getCurveData.
18411
18412 This method assumes that the \ref getTraverse test has returned true, so the
18413 segment definitely traverses the visible region 5 when going from \a
18414 prevRegion to \a currentRegion.
18415
18416 In certain situations it is not sufficient to merely generate the entry and
18417 exit points of the segment into/out of region 5, as \ref getTraverse provides.
18418 It may happen that a single segment, in addition to traversing region 5, skips
18419 another region outside of region 5, which makes it necessary to add an
18420 optimized corner point there (very similar to the job \ref
18421 getOptimizedCornerPoints does for segments that are completely in outside
18422 regions and don't traverse 5).
18423
18424 As an example, consider a segment going from region 1 to region 6, traversing
18425 the lower left corner of region 5. In this configuration, the segment
18426 additionally crosses the border between region 1 and 2 before entering
18427 region 5. This makes it necessary to add an additional point in the top left
18428 corner, before adding the optimized traverse points. So in this case, the
18429 output parameter \a beforeTraverse will contain the top left corner point, and
18430 \a afterTraverse will be empty.
18431
18432 In some cases, such as when going from region 1 to 9, it may even be necessary
18433 to add additional corner points before and after the traverse. Then both \a
18434 beforeTraverse and \a afterTraverse return the respective corner points.
18435 */
18436 void QCPCurve::getTraverseCornerPoints(int prevRegion, int currentRegion,
18437 double rectLeft, double rectTop,
18438 double rectRight, double rectBottom,
18439 QVector<QPointF> &beforeTraverse,
18440 QVector<QPointF> &afterTraverse) const {
18441 switch (prevRegion) {
18442 case 1: {
18443 switch (currentRegion) {
18444 case 6: {
18445 beforeTraverse << coordsToPixels(rectLeft, rectTop);
18446 break;
18447 }
18448 case 9: {
18449 beforeTraverse << coordsToPixels(rectLeft, rectTop);
18450 afterTraverse << coordsToPixels(rectRight, rectBottom);
18451 break;
18452 }
18453 case 8: {
18454 beforeTraverse << coordsToPixels(rectLeft, rectTop);
18455 break;
18456 }
18457 }
18458 break;
18459 }
18460 case 2: {
18461 switch (currentRegion) {
18462 case 7: {
18463 afterTraverse << coordsToPixels(rectRight, rectTop);
18464 break;
18465 }
18466 case 9: {
18467 afterTraverse << coordsToPixels(rectRight, rectBottom);
18468 break;
18469 }
18470 }
18471 break;
18472 }
18473 case 3: {
18474 switch (currentRegion) {
18475 case 4: {
18476 beforeTraverse << coordsToPixels(rectLeft, rectBottom);
18477 break;
18478 }
18479 case 7: {
18480 beforeTraverse << coordsToPixels(rectLeft, rectBottom);
18481 afterTraverse << coordsToPixels(rectRight, rectTop);
18482 break;
18483 }
18484 case 8: {
18485 beforeTraverse << coordsToPixels(rectLeft, rectBottom);
18486 break;
18487 }
18488 }
18489 break;
18490 }
18491 case 4: {
18492 switch (currentRegion) {
18493 case 3: {
18494 afterTraverse << coordsToPixels(rectLeft, rectBottom);
18495 break;
18496 }
18497 case 9: {
18498 afterTraverse << coordsToPixels(rectRight, rectBottom);
18499 break;
18500 }
18501 }
18502 break;
18503 }
18504 case 5: {
18505 break;
18506 } // shouldn't happen because this method only handles full traverses
18507 case 6: {
18508 switch (currentRegion) {
18509 case 1: {
18510 afterTraverse << coordsToPixels(rectLeft, rectTop);
18511 break;
18512 }
18513 case 7: {
18514 afterTraverse << coordsToPixels(rectRight, rectTop);
18515 break;
18516 }
18517 }
18518 break;
18519 }
18520 case 7: {
18521 switch (currentRegion) {
18522 case 2: {
18523 beforeTraverse << coordsToPixels(rectRight, rectTop);
18524 break;
18525 }
18526 case 3: {
18527 beforeTraverse << coordsToPixels(rectRight, rectTop);
18528 afterTraverse << coordsToPixels(rectLeft, rectBottom);
18529 break;
18530 }
18531 case 6: {
18532 beforeTraverse << coordsToPixels(rectRight, rectTop);
18533 break;
18534 }
18535 }
18536 break;
18537 }
18538 case 8: {
18539 switch (currentRegion) {
18540 case 1: {
18541 afterTraverse << coordsToPixels(rectLeft, rectTop);
18542 break;
18543 }
18544 case 3: {
18545 afterTraverse << coordsToPixels(rectLeft, rectBottom);
18546 break;
18547 }
18548 }
18549 break;
18550 }
18551 case 9: {
18552 switch (currentRegion) {
18553 case 2: {
18554 beforeTraverse << coordsToPixels(rectRight, rectBottom);
18555 break;
18556 }
18557 case 1: {
18558 beforeTraverse << coordsToPixels(rectRight, rectBottom);
18559 afterTraverse << coordsToPixels(rectLeft, rectTop);
18560 break;
18561 }
18562 case 4: {
18563 beforeTraverse << coordsToPixels(rectRight, rectBottom);
18564 break;
18565 }
18566 }
18567 break;
18568 }
18569 }
18570 }
18571
18572 /*! \internal
18573
18574 Calculates the (minimum) distance (in pixels) the curve's representation has
18575 from the given \a pixelPoint in pixels. This is used to determine whether the
18576 curve was clicked or not, e.g. in \ref selectTest.
18577 */
18578 double QCPCurve::pointDistance(const QPointF &pixelPoint) const {
18579 if (mData->isEmpty()) {
18580 qDebug() << Q_FUNC_INFO << "requested point distance on curve" << mName
18581 << "without data";
18582 return 500;
18583 }
18584 if (mData->size() == 1) {
18585 QPointF dataPoint = coordsToPixels(mData->constBegin().key(),
18586 mData->constBegin().value().value);
18587 return QVector2D(dataPoint - pixelPoint).length();
18588 }
18589
18590 // calculate minimum distance to line segments:
18591 QVector<QPointF> *lineData = new QVector<QPointF>;
18592 getCurveData(lineData);
18593 double minDistSqr = std::numeric_limits<double>::max();
18594 for (int i = 0; i < lineData->size() - 1; ++i) {
18595 double currentDistSqr =
18596 distSqrToLine(lineData->at(i), lineData->at(i + 1), pixelPoint);
18597 if (currentDistSqr < minDistSqr) minDistSqr = currentDistSqr;
18598 }
18599 delete lineData;
18600 return qSqrt(minDistSqr);
18601 }
18602
18603 /* inherits documentation from base class */
18604 QCPRange QCPCurve::getKeyRange(bool &foundRange,
18605 SignDomain inSignDomain) const {
18606 QCPRange range;
18607 bool haveLower = false;
18608 bool haveUpper = false;
18609
18610 double current;
18611
18612 QCPCurveDataMap::const_iterator it = mData->constBegin();
18613 while (it != mData->constEnd()) {
18614 current = it.value().key;
18615 if (!qIsNaN(current) && !qIsNaN(it.value().value)) {
18616 if (inSignDomain == sdBoth ||
18617 (inSignDomain == sdNegative && current < 0) ||
18618 (inSignDomain == sdPositive && current > 0)) {
18619 if (current < range.lower || !haveLower) {
18620 range.lower = current;
18621 haveLower = true;
18622 }
18623 if (current > range.upper || !haveUpper) {
18624 range.upper = current;
18625 haveUpper = true;
18626 }
18627 }
18628 }
18629 ++it;
18630 }
18631
18632 foundRange = haveLower && haveUpper;
18633 return range;
18634 }
18635
18636 /* inherits documentation from base class */
18637 QCPRange QCPCurve::getValueRange(bool &foundRange,
18638 SignDomain inSignDomain) const {
18639 QCPRange range;
18640 bool haveLower = false;
18641 bool haveUpper = false;
18642
18643 double current;
18644
18645 QCPCurveDataMap::const_iterator it = mData->constBegin();
18646 while (it != mData->constEnd()) {
18647 current = it.value().value;
18648 if (!qIsNaN(current) && !qIsNaN(it.value().key)) {
18649 if (inSignDomain == sdBoth ||
18650 (inSignDomain == sdNegative && current < 0) ||
18651 (inSignDomain == sdPositive && current > 0)) {
18652 if (current < range.lower || !haveLower) {
18653 range.lower = current;
18654 haveLower = true;
18655 }
18656 if (current > range.upper || !haveUpper) {
18657 range.upper = current;
18658 haveUpper = true;
18659 }
18660 }
18661 }
18662 ++it;
18663 }
18664
18665 foundRange = haveLower && haveUpper;
18666 return range;
18667 }
18668
18669 ////////////////////////////////////////////////////////////////////////////////////////////////////
18670 //////////////////// QCPBarsGroup
18671 ////////////////////////////////////////////////////////////////////////////////////////////////////
18672
18673 /*! \class QCPBarsGroup
18674 \brief Groups multiple QCPBars together so they appear side by side
18675
18676 \image html QCPBarsGroup.png
18677
18678 When showing multiple QCPBars in one plot which have bars at identical keys,
18679 it may be desirable to have them appearing next to each other at each key.
18680 This is what adding the respective QCPBars plottables to a QCPBarsGroup
18681 achieves. (An alternative approach is to stack them on top of each other, see
18682 \ref QCPBars::moveAbove.)
18683
18684 \section qcpbarsgroup-usage Usage
18685
18686 To add a QCPBars plottable to the group, create a new group and then add the
18687 respective bars intances: \snippet
18688 documentation/doc-code-snippets/mainwindow.cpp qcpbarsgroup-creation
18689 Alternatively to appending to the group like shown above, you can also set the
18690 group on the QCPBars plottable via \ref QCPBars::setBarsGroup.
18691
18692 The spacing between the bars can be configured via \ref setSpacingType and
18693 \ref setSpacing. The bars in this group appear in the plot in the order they
18694 were appended. To insert a bars plottable at a certain index position, or to
18695 reposition a bars plottable which is already in the group, use \ref insert.
18696
18697 To remove specific bars from the group, use either \ref remove or call \ref
18698 QCPBars::setBarsGroup "QCPBars::setBarsGroup(0)" on the respective bars
18699 plottable.
18700
18701 To clear the entire group, call \ref clear, or simply delete the group.
18702
18703 \section qcpbarsgroup-example Example
18704
18705 The image above is generated with the following code:
18706 \snippet documentation/doc-image-generator/mainwindow.cpp qcpbarsgroup-example
18707 */
18708
18709 /* start of documentation of inline functions */
18710
18711 /*! \fn QList<QCPBars*> QCPBarsGroup::bars() const
18712
18713 Returns all bars currently in this group.
18714
18715 \see bars(int index)
18716 */
18717
18718 /*! \fn int QCPBarsGroup::size() const
18719
18720 Returns the number of QCPBars plottables that are part of this group.
18721
18722 */
18723
18724 /*! \fn bool QCPBarsGroup::isEmpty() const
18725
18726 Returns whether this bars group is empty.
18727
18728 \see size
18729 */
18730
18731 /*! \fn bool QCPBarsGroup::contains(QCPBars *bars)
18732
18733 Returns whether the specified \a bars plottable is part of this group.
18734
18735 */
18736
18737 /* end of documentation of inline functions */
18738
18739 /*!
18740 Constructs a new bars group for the specified QCustomPlot instance.
18741 */
18742 QCPBarsGroup::QCPBarsGroup(QCustomPlot *parentPlot)
18743 : QObject(parentPlot),
18744 mParentPlot(parentPlot),
18745 mSpacingType(stAbsolute),
18746 mSpacing(4) {}
18747
18748 QCPBarsGroup::~QCPBarsGroup() { clear(); }
18749
18750 /*!
18751 Sets how the spacing between adjacent bars is interpreted. See \ref
18752 SpacingType.
18753
18754 The actual spacing can then be specified with \ref setSpacing.
18755
18756 \see setSpacing
18757 */
18758 void QCPBarsGroup::setSpacingType(SpacingType spacingType) {
18759 mSpacingType = spacingType;
18760 }
18761
18762 /*!
18763 Sets the spacing between adjacent bars. What the number passed as \a spacing
18764 actually means, is defined by the current \ref SpacingType, which can be set
18765 with \ref setSpacingType.
18766
18767 \see setSpacingType
18768 */
18769 void QCPBarsGroup::setSpacing(double spacing) { mSpacing = spacing; }
18770
18771 /*!
18772 Returns the QCPBars instance with the specified \a index in this group. If no
18773 such QCPBars exists, returns 0.
18774
18775 \see bars(), size
18776 */
18777 QCPBars *QCPBarsGroup::bars(int index) const {
18778 if (index >= 0 && index < mBars.size()) {
18779 return mBars.at(index);
18780 } else {
18781 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
18782 return 0;
18783 }
18784 }
18785
18786 /*!
18787 Removes all QCPBars plottables from this group.
18788
18789 \see isEmpty
18790 */
18791 void QCPBarsGroup::clear() {
18792 foreach (
18793 QCPBars *bars,
18794 mBars) // since foreach takes a copy, removing bars in the loop is okay
18795 bars->setBarsGroup(0); // removes itself via removeBars
18796 }
18797
18798 /*!
18799 Adds the specified \a bars plottable to this group. Alternatively, you can
18800 also use \ref QCPBars::setBarsGroup on the \a bars instance.
18801
18802 \see insert, remove
18803 */
18804 void QCPBarsGroup::append(QCPBars *bars) {
18805 if (!bars) {
18806 qDebug() << Q_FUNC_INFO << "bars is 0";
18807 return;
18808 }
18809
18810 if (!mBars.contains(bars))
18811 bars->setBarsGroup(this);
18812 else
18813 qDebug() << Q_FUNC_INFO << "bars plottable is already in this bars group:"
18814 << reinterpret_cast<quintptr>(bars);
18815 }
18816
18817 /*!
18818 Inserts the specified \a bars plottable into this group at the specified index
18819 position \a i. This gives you full control over the ordering of the bars.
18820
18821 \a bars may already be part of this group. In that case, \a bars is just moved
18822 to the new index position.
18823
18824 \see append, remove
18825 */
18826 void QCPBarsGroup::insert(int i, QCPBars *bars) {
18827 if (!bars) {
18828 qDebug() << Q_FUNC_INFO << "bars is 0";
18829 return;
18830 }
18831
18832 // first append to bars list normally:
18833 if (!mBars.contains(bars)) bars->setBarsGroup(this);
18834 // then move to according position:
18835 mBars.move(mBars.indexOf(bars), qBound(0, i, mBars.size() - 1));
18836 }
18837
18838 /*!
18839 Removes the specified \a bars plottable from this group.
18840
18841 \see contains, clear
18842 */
18843 void QCPBarsGroup::remove(QCPBars *bars) {
18844 if (!bars) {
18845 qDebug() << Q_FUNC_INFO << "bars is 0";
18846 return;
18847 }
18848
18849 if (mBars.contains(bars))
18850 bars->setBarsGroup(0);
18851 else
18852 qDebug() << Q_FUNC_INFO << "bars plottable is not in this bars group:"
18853 << reinterpret_cast<quintptr>(bars);
18854 }
18855
18856 /*! \internal
18857
18858 Adds the specified \a bars to the internal mBars list of bars. This method
18859 does not change the barsGroup property on \a bars.
18860
18861 \see unregisterBars
18862 */
18863 void QCPBarsGroup::registerBars(QCPBars *bars) {
18864 if (!mBars.contains(bars)) mBars.append(bars);
18865 }
18866
18867 /*! \internal
18868
18869 Removes the specified \a bars from the internal mBars list of bars. This
18870 method does not change the barsGroup property on \a bars.
18871
18872 \see registerBars
18873 */
18874 void QCPBarsGroup::unregisterBars(QCPBars *bars) { mBars.removeOne(bars); }
18875
18876 /*! \internal
18877
18878 Returns the pixel offset in the key dimension the specified \a bars plottable
18879 should have at the given key coordinate \a keyCoord. The offset is relative to
18880 the pixel position of the key coordinate \a keyCoord.
18881 */
18882 double QCPBarsGroup::keyPixelOffset(const QCPBars *bars, double keyCoord) {
18883 // find list of all base bars in case some mBars are stacked:
18884 QList<const QCPBars *> baseBars;
18885 foreach (const QCPBars *b, mBars) {
18886 while (b->barBelow()) b = b->barBelow();
18887 if (!baseBars.contains(b)) baseBars.append(b);
18888 }
18889 // find base bar this "bars" is stacked on:
18890 const QCPBars *thisBase = bars;
18891 while (thisBase->barBelow()) thisBase = thisBase->barBelow();
18892
18893 // determine key pixel offset of this base bars considering all other base
18894 // bars in this barsgroup:
18895 double result = 0;
18896 int index = baseBars.indexOf(thisBase);
18897 if (index >= 0) {
18898 int startIndex;
18899 double lowerPixelWidth, upperPixelWidth;
18900 if (baseBars.size() % 2 == 1 &&
18901 index == (baseBars.size() - 1) /
18902 2) // is center bar (int division on purpose)
18903 {
18904 return result;
18905 } else if (index <
18906 (baseBars.size() - 1) / 2.0) // bar is to the left of center
18907 {
18908 if (baseBars.size() % 2 == 0) // even number of bars
18909 {
18910 startIndex = baseBars.size() / 2 - 1;
18911 result -= getPixelSpacing(baseBars.at(startIndex), keyCoord) *
18912 0.5; // half of middle spacing
18913 } else // uneven number of bars
18914 {
18915 startIndex = (baseBars.size() - 1) / 2 - 1;
18916 baseBars.at((baseBars.size() - 1) / 2)
18917 ->getPixelWidth(keyCoord, lowerPixelWidth, upperPixelWidth);
18918 result -= qAbs(upperPixelWidth - lowerPixelWidth) *
18919 0.5; // half of center bar
18920 result -= getPixelSpacing(baseBars.at((baseBars.size() - 1) / 2),
18921 keyCoord); // center bar spacing
18922 }
18923 for (int i = startIndex; i > index;
18924 --i) // add widths and spacings of bars in between center and our
18925 // bars
18926 {
18927 baseBars.at(i)->getPixelWidth(keyCoord, lowerPixelWidth,
18928 upperPixelWidth);
18929 result -= qAbs(upperPixelWidth - lowerPixelWidth);
18930 result -= getPixelSpacing(baseBars.at(i), keyCoord);
18931 }
18932 // finally half of our bars width:
18933 baseBars.at(index)->getPixelWidth(keyCoord, lowerPixelWidth,
18934 upperPixelWidth);
18935 result -= qAbs(upperPixelWidth - lowerPixelWidth) * 0.5;
18936 } else // bar is to the right of center
18937 {
18938 if (baseBars.size() % 2 == 0) // even number of bars
18939 {
18940 startIndex = baseBars.size() / 2;
18941 result += getPixelSpacing(baseBars.at(startIndex), keyCoord) *
18942 0.5; // half of middle spacing
18943 } else // uneven number of bars
18944 {
18945 startIndex = (baseBars.size() - 1) / 2 + 1;
18946 baseBars.at((baseBars.size() - 1) / 2)
18947 ->getPixelWidth(keyCoord, lowerPixelWidth, upperPixelWidth);
18948 result += qAbs(upperPixelWidth - lowerPixelWidth) *
18949 0.5; // half of center bar
18950 result += getPixelSpacing(baseBars.at((baseBars.size() - 1) / 2),
18951 keyCoord); // center bar spacing
18952 }
18953 for (int i = startIndex; i < index;
18954 ++i) // add widths and spacings of bars in between center and our
18955 // bars
18956 {
18957 baseBars.at(i)->getPixelWidth(keyCoord, lowerPixelWidth,
18958 upperPixelWidth);
18959 result += qAbs(upperPixelWidth - lowerPixelWidth);
18960 result += getPixelSpacing(baseBars.at(i), keyCoord);
18961 }
18962 // finally half of our bars width:
18963 baseBars.at(index)->getPixelWidth(keyCoord, lowerPixelWidth,
18964 upperPixelWidth);
18965 result += qAbs(upperPixelWidth - lowerPixelWidth) * 0.5;
18966 }
18967 }
18968 return result;
18969 }
18970
18971 /*! \internal
18972
18973 Returns the spacing in pixels which is between this \a bars and the following
18974 one, both at the key coordinate \a keyCoord.
18975
18976 \note Typically the returned value doesn't depend on \a bars or \a keyCoord.
18977 \a bars is only needed to get acces to the key axis transformation and axis
18978 rect for the modes \ref stAxisRectRatio and \ref stPlotCoords. The \a keyCoord
18979 is only relevant for spacings given in \ref stPlotCoords on a logarithmic
18980 axis.
18981 */
18982 double QCPBarsGroup::getPixelSpacing(const QCPBars *bars, double keyCoord) {
18983 switch (mSpacingType) {
18984 case stAbsolute: {
18985 return mSpacing;
18986 }
18987 case stAxisRectRatio: {
18988 if (bars->keyAxis()->orientation() == Qt::Horizontal)
18989 return bars->keyAxis()->axisRect()->width() * mSpacing;
18990 else
18991 return bars->keyAxis()->axisRect()->height() * mSpacing;
18992 }
18993 case stPlotCoords: {
18994 double keyPixel = bars->keyAxis()->coordToPixel(keyCoord);
18995 return bars->keyAxis()->coordToPixel(keyCoord + mSpacing) - keyPixel;
18996 }
18997 }
18998 return 0;
18999 }
19000
19001 ////////////////////////////////////////////////////////////////////////////////////////////////////
19002 //////////////////// QCPBarData
19003 ////////////////////////////////////////////////////////////////////////////////////////////////////
19004
19005 /*! \class QCPBarData
19006 \brief Holds the data of one single data point (one bar) for QCPBars.
19007
19008 The container for storing multiple data points is \ref QCPBarDataMap.
19009
19010 The stored data is:
19011 \li \a key: coordinate on the key axis of this bar
19012 \li \a value: height coordinate on the value axis of this bar
19013
19014 \see QCPBarDataaMap
19015 */
19016
19017 /*!
19018 Constructs a bar data point with key and value set to zero.
19019 */
19020 QCPBarData::QCPBarData() : key(0), value(0) {}
19021
19022 /*!
19023 Constructs a bar data point with the specified \a key and \a value.
19024 */
19025 QCPBarData::QCPBarData(double key, double value) : key(key), value(value) {}
19026
19027 ////////////////////////////////////////////////////////////////////////////////////////////////////
19028 //////////////////// QCPBars
19029 ////////////////////////////////////////////////////////////////////////////////////////////////////
19030
19031 /*! \class QCPBars
19032 \brief A plottable representing a bar chart in a plot.
19033
19034 \image html QCPBars.png
19035
19036 To plot data, assign it with the \ref setData or \ref addData functions.
19037
19038 \section appearance Changing the appearance
19039
19040 The appearance of the bars is determined by the pen and the brush (\ref
19041 setPen, \ref setBrush). The width of the individual bars can be controlled
19042 with \ref setWidthType and \ref setWidth.
19043
19044 Bar charts are stackable. This means, two QCPBars plottables can be placed on
19045 top of each other (see \ref QCPBars::moveAbove). So when two bars are at the
19046 same key position, they will appear stacked.
19047
19048 If you would like to group multiple QCPBars plottables together so they appear
19049 side by side as shown below, use QCPBarsGroup.
19050
19051 \image html QCPBarsGroup.png
19052
19053 \section usage Usage
19054
19055 Like all data representing objects in QCustomPlot, the QCPBars is a plottable
19056 (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
19057 (QCustomPlot::plottable, QCustomPlot::addPlottable,
19058 QCustomPlot::removePlottable, etc.)
19059
19060 Usually, you first create an instance:
19061 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-creation-1
19062 add it to the customPlot with QCustomPlot::addPlottable:
19063 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-creation-2
19064 and then modify the properties of the newly created plottable, e.g.:
19065 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-creation-3
19066 */
19067
19068 /* start of documentation of inline functions */
19069
19070 /*! \fn QCPBars *QCPBars::barBelow() const
19071 Returns the bars plottable that is directly below this bars plottable.
19072 If there is no such plottable, returns 0.
19073
19074 \see barAbove, moveBelow, moveAbove
19075 */
19076
19077 /*! \fn QCPBars *QCPBars::barAbove() const
19078 Returns the bars plottable that is directly above this bars plottable.
19079 If there is no such plottable, returns 0.
19080
19081 \see barBelow, moveBelow, moveAbove
19082 */
19083
19084 /* end of documentation of inline functions */
19085
19086 /*!
19087 Constructs a bar chart which uses \a keyAxis as its key axis ("x") and \a
19088 valueAxis as its value axis ("y"). \a keyAxis and \a valueAxis must reside in
19089 the same QCustomPlot instance and not have the same orientation. If either of
19090 these restrictions is violated, a corresponding message is printed to the
19091 debug output (qDebug), the construction is not aborted, though.
19092
19093 The constructed QCPBars can be added to the plot with
19094 QCustomPlot::addPlottable, QCustomPlot then takes ownership of the bar chart.
19095 */
19096 QCPBars::QCPBars(QCPAxis *keyAxis, QCPAxis *valueAxis)
19097 : QCPAbstractPlottable(keyAxis, valueAxis),
19098 mData(new QCPBarDataMap),
19099 mWidth(0.75),
19100 mWidthType(wtPlotCoords),
19101 mBarsGroup(0),
19102 mBaseValue(0) {
19103 // modify inherited properties from abstract plottable:
19104 mPen.setColor(Qt::blue);
19105 mPen.setStyle(Qt::SolidLine);
19106 mBrush.setColor(QColor(40, 50, 255, 30));
19107 mBrush.setStyle(Qt::SolidPattern);
19108 mSelectedPen = mPen;
19109 mSelectedPen.setWidthF(2.5);
19110 mSelectedPen.setColor(QColor(80, 80, 255)); // lighter than Qt::blue of mPen
19111 mSelectedBrush = mBrush;
19112 }
19113
19114 QCPBars::~QCPBars() {
19115 setBarsGroup(0);
19116 if (mBarBelow || mBarAbove)
19117 connectBars(mBarBelow.data(),
19118 mBarAbove.data()); // take this bar out of any stacking
19119 delete mData;
19120 }
19121
19122 /*!
19123 Sets the width of the bars.
19124
19125 How the number passed as \a width is interpreted (e.g. screen pixels, plot
19126 coordinates,...), depends on the currently set width type, see \ref
19127 setWidthType and \ref WidthType.
19128 */
19129 void QCPBars::setWidth(double width) { mWidth = width; }
19130
19131 /*!
19132 Sets how the width of the bars is defined. See the documentation of \ref
19133 WidthType for an explanation of the possible values for \a widthType.
19134
19135 The default value is \ref wtPlotCoords.
19136
19137 \see setWidth
19138 */
19139 void QCPBars::setWidthType(QCPBars::WidthType widthType) {
19140 mWidthType = widthType;
19141 }
19142
19143 /*!
19144 Sets to which QCPBarsGroup this QCPBars instance belongs to. Alternatively,
19145 you can also use \ref QCPBarsGroup::append.
19146
19147 To remove this QCPBars from any group, set \a barsGroup to 0.
19148 */
19149 void QCPBars::setBarsGroup(QCPBarsGroup *barsGroup) {
19150 // deregister at old group:
19151 if (mBarsGroup) mBarsGroup->unregisterBars(this);
19152 mBarsGroup = barsGroup;
19153 // register at new group:
19154 if (mBarsGroup) mBarsGroup->registerBars(this);
19155 }
19156
19157 /*!
19158 Sets the base value of this bars plottable.
19159
19160 The base value defines where on the value coordinate the bars start. How far
19161 the bars extend from the base value is given by their individual value data.
19162 For example, if the base value is set to 1, a bar with data value 2 will have
19163 its lowest point at value coordinate 1 and highest point at 3.
19164
19165 For stacked bars, only the base value of the bottom-most QCPBars has meaning.
19166
19167 The default base value is 0.
19168 */
19169 void QCPBars::setBaseValue(double baseValue) { mBaseValue = baseValue; }
19170
19171 /*!
19172 Replaces the current data with the provided \a data.
19173
19174 If \a copy is set to true, data points in \a data will only be copied. if
19175 false, the plottable takes ownership of the passed data and replaces the
19176 internal data pointer with it. This is significantly faster than copying for
19177 large datasets.
19178 */
19179 void QCPBars::setData(QCPBarDataMap *data, bool copy) {
19180 if (mData == data) {
19181 qDebug() << Q_FUNC_INFO
19182 << "The data pointer is already in (and owned by) this plottable"
19183 << reinterpret_cast<quintptr>(data);
19184 return;
19185 }
19186 if (copy) {
19187 *mData = *data;
19188 } else {
19189 delete mData;
19190 mData = data;
19191 }
19192 }
19193
19194 /*! \overload
19195
19196 Replaces the current data with the provided points in \a key and \a value
19197 tuples. The provided vectors should have equal length. Else, the number of
19198 added points will be the size of the smallest vector.
19199 */
19200 void QCPBars::setData(const QVector<double> &key,
19201 const QVector<double> &value) {
19202 mData->clear();
19203 int n = key.size();
19204 n = qMin(n, value.size());
19205 QCPBarData newData;
19206 for (int i = 0; i < n; ++i) {
19207 newData.key = key[i];
19208 newData.value = value[i];
19209 mData->insertMulti(newData.key, newData);
19210 }
19211 }
19212
19213 /*!
19214 Moves this bars plottable below \a bars. In other words, the bars of this
19215 plottable will appear below the bars of \a bars. The move target \a bars must
19216 use the same key and value axis as this plottable.
19217
19218 Inserting into and removing from existing bar stacking is handled gracefully.
19219 If \a bars already has a bars object below itself, this bars object is
19220 inserted between the two. If this bars object is already between two other
19221 bars, the two other bars will be stacked on top of each other after the
19222 operation.
19223
19224 To remove this bars plottable from any stacking, set \a bars to 0.
19225
19226 \see moveBelow, barAbove, barBelow
19227 */
19228 void QCPBars::moveBelow(QCPBars *bars) {
19229 if (bars == this) return;
19230 if (bars && (bars->keyAxis() != mKeyAxis.data() ||
19231 bars->valueAxis() != mValueAxis.data())) {
19232 qDebug() << Q_FUNC_INFO
19233 << "passed QCPBars* doesn't have same key and value axis as this "
19234 "QCPBars";
19235 return;
19236 }
19237 // remove from stacking:
19238 connectBars(
19239 mBarBelow.data(),
19240 mBarAbove.data()); // Note: also works if one (or both) of them is 0
19241 // if new bar given, insert this bar below it:
19242 if (bars) {
19243 if (bars->mBarBelow) connectBars(bars->mBarBelow.data(), this);
19244 connectBars(this, bars);
19245 }
19246 }
19247
19248 /*!
19249 Moves this bars plottable above \a bars. In other words, the bars of this
19250 plottable will appear above the bars of \a bars. The move target \a bars must
19251 use the same key and value axis as this plottable.
19252
19253 Inserting into and removing from existing bar stacking is handled gracefully.
19254 If \a bars already has a bars object above itself, this bars object is
19255 inserted between the two. If this bars object is already between two other
19256 bars, the two other bars will be stacked on top of each other after the
19257 operation.
19258
19259 To remove this bars plottable from any stacking, set \a bars to 0.
19260
19261 \see moveBelow, barBelow, barAbove
19262 */
19263 void QCPBars::moveAbove(QCPBars *bars) {
19264 if (bars == this) return;
19265 if (bars && (bars->keyAxis() != mKeyAxis.data() ||
19266 bars->valueAxis() != mValueAxis.data())) {
19267 qDebug() << Q_FUNC_INFO
19268 << "passed QCPBars* doesn't have same key and value axis as this "
19269 "QCPBars";
19270 return;
19271 }
19272 // remove from stacking:
19273 connectBars(
19274 mBarBelow.data(),
19275 mBarAbove.data()); // Note: also works if one (or both) of them is 0
19276 // if new bar given, insert this bar above it:
19277 if (bars) {
19278 if (bars->mBarAbove) connectBars(this, bars->mBarAbove.data());
19279 connectBars(bars, this);
19280 }
19281 }
19282
19283 /*!
19284 Adds the provided data points in \a dataMap to the current data.
19285 \see removeData
19286 */
19287 void QCPBars::addData(const QCPBarDataMap &dataMap) { mData->unite(dataMap); }
19288
19289 /*! \overload
19290 Adds the provided single data point in \a data to the current data.
19291 \see removeData
19292 */
19293 void QCPBars::addData(const QCPBarData &data) {
19294 mData->insertMulti(data.key, data);
19295 }
19296
19297 /*! \overload
19298 Adds the provided single data point as \a key and \a value tuple to the
19299 current data \see removeData
19300 */
19301 void QCPBars::addData(double key, double value) {
19302 QCPBarData newData;
19303 newData.key = key;
19304 newData.value = value;
19305 mData->insertMulti(newData.key, newData);
19306 }
19307
19308 /*! \overload
19309 Adds the provided data points as \a key and \a value tuples to the current
19310 data. \see removeData
19311 */
19312 void QCPBars::addData(const QVector<double> &keys,
19313 const QVector<double> &values) {
19314 int n = keys.size();
19315 n = qMin(n, values.size());
19316 QCPBarData newData;
19317 for (int i = 0; i < n; ++i) {
19318 newData.key = keys[i];
19319 newData.value = values[i];
19320 mData->insertMulti(newData.key, newData);
19321 }
19322 }
19323
19324 /*!
19325 Removes all data points with key smaller than \a key.
19326 \see addData, clearData
19327 */
19328 void QCPBars::removeDataBefore(double key) {
19329 QCPBarDataMap::iterator it = mData->begin();
19330 while (it != mData->end() && it.key() < key) it = mData->erase(it);
19331 }
19332
19333 /*!
19334 Removes all data points with key greater than \a key.
19335 \see addData, clearData
19336 */
19337 void QCPBars::removeDataAfter(double key) {
19338 if (mData->isEmpty()) return;
19339 QCPBarDataMap::iterator it = mData->upperBound(key);
19340 while (it != mData->end()) it = mData->erase(it);
19341 }
19342
19343 /*!
19344 Removes all data points with key between \a fromKey and \a toKey. if \a
19345 fromKey is greater or equal to \a toKey, the function does nothing. To remove
19346 a single data point with known key, use \ref removeData(double key).
19347
19348 \see addData, clearData
19349 */
19350 void QCPBars::removeData(double fromKey, double toKey) {
19351 if (fromKey >= toKey || mData->isEmpty()) return;
19352 QCPBarDataMap::iterator it = mData->upperBound(fromKey);
19353 QCPBarDataMap::iterator itEnd = mData->upperBound(toKey);
19354 while (it != itEnd) it = mData->erase(it);
19355 }
19356
19357 /*! \overload
19358
19359 Removes a single data point at \a key. If the position is not known with
19360 absolute precision, consider using \ref removeData(double fromKey, double
19361 toKey) with a small fuzziness interval around the suspected position, depeding
19362 on the precision with which the key is known.
19363
19364 \see addData, clearData
19365 */
19366 void QCPBars::removeData(double key) { mData->remove(key); }
19367
19368 /*!
19369 Removes all data points.
19370 \see removeData, removeDataAfter, removeDataBefore
19371 */
19372 void QCPBars::clearData() { mData->clear(); }
19373
19374 /* inherits documentation from base class */
19375 double QCPBars::selectTest(const QPointF &pos, bool onlySelectable,
19376 QVariant *details) const {
19377 Q_UNUSED(details)
19378 if (onlySelectable && !mSelectable) return -1;
19379 if (!mKeyAxis || !mValueAxis) {
19380 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
19381 return -1;
19382 }
19383
19384 if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint())) {
19385 QCPBarDataMap::ConstIterator it;
19386 for (it = mData->constBegin(); it != mData->constEnd(); ++it) {
19387 if (getBarPolygon(it.value().key, it.value().value)
19388 .boundingRect()
19389 .contains(pos))
19390 return mParentPlot->selectionTolerance() * 0.99;
19391 }
19392 }
19393 return -1;
19394 }
19395
19396 /* inherits documentation from base class */
19397 void QCPBars::draw(QCPPainter *painter) {
19398 if (!mKeyAxis || !mValueAxis) {
19399 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
19400 return;
19401 }
19402 if (mData->isEmpty()) return;
19403
19404 QCPBarDataMap::const_iterator it, lower, upperEnd;
19405 getVisibleDataBounds(lower, upperEnd);
19406 for (it = lower; it != upperEnd; ++it) {
19407 // check data validity if flag set:
19408 #ifdef QCUSTOMPLOT_CHECK_DATA
19409 if (QCP::isInvalidData(it.value().key, it.value().value))
19410 qDebug() << Q_FUNC_INFO << "Data point at" << it.key()
19411 << "of drawn range invalid."
19412 << "Plottable name:" << name();
19413 #endif
19414 QPolygonF barPolygon = getBarPolygon(it.key(), it.value().value);
19415 // draw bar fill:
19416 if (mainBrush().style() != Qt::NoBrush &&
19417 mainBrush().color().alpha() != 0) {
19418 applyFillAntialiasingHint(painter);
19419 painter->setPen(Qt::NoPen);
19420 painter->setBrush(mainBrush());
19421 painter->drawPolygon(barPolygon);
19422 }
19423 // draw bar line:
19424 if (mainPen().style() != Qt::NoPen && mainPen().color().alpha() != 0) {
19425 applyDefaultAntialiasingHint(painter);
19426 painter->setPen(mainPen());
19427 painter->setBrush(Qt::NoBrush);
19428 painter->drawPolyline(barPolygon);
19429 }
19430 }
19431 }
19432
19433 /* inherits documentation from base class */
19434 void QCPBars::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const {
19435 // draw filled rect:
19436 applyDefaultAntialiasingHint(painter);
19437 painter->setBrush(mBrush);
19438 painter->setPen(mPen);
19439 QRectF r = QRectF(0, 0, rect.width() * 0.67, rect.height() * 0.67);
19440 r.moveCenter(rect.center());
19441 painter->drawRect(r);
19442 }
19443
19444 /*! \internal
19445
19446 called by \ref draw to determine which data (key) range is visible at the
19447 current key axis range setting, so only that needs to be processed. It also
19448 takes into account the bar width.
19449
19450 \a lower returns an iterator to the lowest data point that needs to be taken
19451 into account when plotting. Note that in order to get a clean plot all the way
19452 to the edge of the axis rect, \a lower may still be just outside the visible
19453 range.
19454
19455 \a upperEnd returns an iterator one higher than the highest visible data
19456 point. Same as before, \a upperEnd may also lie just outside of the visible
19457 range.
19458
19459 if the bars plottable contains no data, both \a lower and \a upperEnd point to
19460 constEnd.
19461 */
19462 void QCPBars::getVisibleDataBounds(
19463 QCPBarDataMap::const_iterator &lower,
19464 QCPBarDataMap::const_iterator &upperEnd) const {
19465 if (!mKeyAxis) {
19466 qDebug() << Q_FUNC_INFO << "invalid key axis";
19467 return;
19468 }
19469 if (mData->isEmpty()) {
19470 lower = mData->constEnd();
19471 upperEnd = mData->constEnd();
19472 return;
19473 }
19474
19475 // get visible data range as QMap iterators
19476 lower = mData->lowerBound(mKeyAxis.data()->range().lower);
19477 upperEnd = mData->upperBound(mKeyAxis.data()->range().upper);
19478 double lowerPixelBound =
19479 mKeyAxis.data()->coordToPixel(mKeyAxis.data()->range().lower);
19480 double upperPixelBound =
19481 mKeyAxis.data()->coordToPixel(mKeyAxis.data()->range().upper);
19482 bool isVisible = false;
19483 // walk left from lbound to find lower bar that actually is completely outside
19484 // visible pixel range:
19485 QCPBarDataMap::const_iterator it = lower;
19486 while (it != mData->constBegin()) {
19487 --it;
19488 QRectF barBounds =
19489 getBarPolygon(it.value().key, it.value().value).boundingRect();
19490 if (mKeyAxis.data()->orientation() == Qt::Horizontal)
19491 isVisible = ((!mKeyAxis.data()->rangeReversed() &&
19492 barBounds.right() >= lowerPixelBound) ||
19493 (mKeyAxis.data()->rangeReversed() &&
19494 barBounds.left() <= lowerPixelBound));
19495 else // keyaxis is vertical
19496 isVisible = ((!mKeyAxis.data()->rangeReversed() &&
19497 barBounds.top() <= lowerPixelBound) ||
19498 (mKeyAxis.data()->rangeReversed() &&
19499 barBounds.bottom() >= lowerPixelBound));
19500 if (isVisible)
19501 lower = it;
19502 else
19503 break;
19504 }
19505 // walk right from ubound to find upper bar that actually is completely
19506 // outside visible pixel range:
19507 it = upperEnd;
19508 while (it != mData->constEnd()) {
19509 QRectF barBounds =
19510 getBarPolygon(upperEnd.value().key, upperEnd.value().value)
19511 .boundingRect();
19512 if (mKeyAxis.data()->orientation() == Qt::Horizontal)
19513 isVisible = ((!mKeyAxis.data()->rangeReversed() &&
19514 barBounds.left() <= upperPixelBound) ||
19515 (mKeyAxis.data()->rangeReversed() &&
19516 barBounds.right() >= upperPixelBound));
19517 else // keyaxis is vertical
19518 isVisible = ((!mKeyAxis.data()->rangeReversed() &&
19519 barBounds.bottom() >= upperPixelBound) ||
19520 (mKeyAxis.data()->rangeReversed() &&
19521 barBounds.top() <= upperPixelBound));
19522 if (isVisible)
19523 upperEnd = it + 1;
19524 else
19525 break;
19526 ++it;
19527 }
19528 }
19529
19530 /*! \internal
19531
19532 Returns the polygon of a single bar with \a key and \a value. The Polygon is
19533 open at the bottom and shifted according to the bar stacking (see \ref
19534 moveAbove) and base value (see \ref setBaseValue).
19535 */
19536 QPolygonF QCPBars::getBarPolygon(double key, double value) const {
19537 QCPAxis *keyAxis = mKeyAxis.data();
19538 QCPAxis *valueAxis = mValueAxis.data();
19539 if (!keyAxis || !valueAxis) {
19540 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
19541 return QPolygonF();
19542 }
19543
19544 QPolygonF result;
19545 double lowerPixelWidth, upperPixelWidth;
19546 getPixelWidth(key, lowerPixelWidth, upperPixelWidth);
19547 double base = getStackedBaseValue(key, value >= 0);
19548 double basePixel = valueAxis->coordToPixel(base);
19549 double valuePixel = valueAxis->coordToPixel(base + value);
19550 double keyPixel = keyAxis->coordToPixel(key);
19551 if (mBarsGroup) keyPixel += mBarsGroup->keyPixelOffset(this, key);
19552 if (keyAxis->orientation() == Qt::Horizontal) {
19553 result << QPointF(keyPixel + lowerPixelWidth, basePixel);
19554 result << QPointF(keyPixel + lowerPixelWidth, valuePixel);
19555 result << QPointF(keyPixel + upperPixelWidth, valuePixel);
19556 result << QPointF(keyPixel + upperPixelWidth, basePixel);
19557 } else {
19558 result << QPointF(basePixel, keyPixel + lowerPixelWidth);
19559 result << QPointF(valuePixel, keyPixel + lowerPixelWidth);
19560 result << QPointF(valuePixel, keyPixel + upperPixelWidth);
19561 result << QPointF(basePixel, keyPixel + upperPixelWidth);
19562 }
19563 return result;
19564 }
19565
19566 /*! \internal
19567
19568 This function is used to determine the width of the bar at coordinate \a key,
19569 according to the specified width (\ref setWidth) and width type (\ref
19570 setWidthType).
19571
19572 The output parameters \a lower and \a upper return the number of pixels the
19573 bar extends to lower and higher keys, relative to the \a key coordinate (so
19574 with a non-reversed horizontal axis, \a lower is negative and \a upper
19575 positive).
19576 */
19577 void QCPBars::getPixelWidth(double key, double &lower, double &upper) const {
19578 switch (mWidthType) {
19579 case wtAbsolute: {
19580 upper = mWidth * 0.5;
19581 lower = -upper;
19582 if (mKeyAxis && (mKeyAxis.data()->rangeReversed() ^
19583 (mKeyAxis.data()->orientation() == Qt::Vertical)))
19584 qSwap(lower, upper);
19585 break;
19586 }
19587 case wtAxisRectRatio: {
19588 if (mKeyAxis && mKeyAxis.data()->axisRect()) {
19589 if (mKeyAxis.data()->orientation() == Qt::Horizontal)
19590 upper = mKeyAxis.data()->axisRect()->width() * mWidth * 0.5;
19591 else
19592 upper = mKeyAxis.data()->axisRect()->height() * mWidth * 0.5;
19593 lower = -upper;
19594 if (mKeyAxis && (mKeyAxis.data()->rangeReversed() ^
19595 (mKeyAxis.data()->orientation() == Qt::Vertical)))
19596 qSwap(lower, upper);
19597 } else
19598 qDebug() << Q_FUNC_INFO << "No key axis or axis rect defined";
19599 break;
19600 }
19601 case wtPlotCoords: {
19602 if (mKeyAxis) {
19603 double keyPixel = mKeyAxis.data()->coordToPixel(key);
19604 upper = mKeyAxis.data()->coordToPixel(key + mWidth * 0.5) - keyPixel;
19605 lower = mKeyAxis.data()->coordToPixel(key - mWidth * 0.5) - keyPixel;
19606 // no need to qSwap(lower, higher) when range reversed, because
19607 // higher/lower are gained by coordinate transform which includes range
19608 // direction
19609 } else
19610 qDebug() << Q_FUNC_INFO << "No key axis defined";
19611 break;
19612 }
19613 }
19614 }
19615
19616 /*! \internal
19617
19618 This function is called to find at which value to start drawing the base of a
19619 bar at \a key, when it is stacked on top of another QCPBars (e.g. with \ref
19620 moveAbove).
19621
19622 positive and negative bars are separated per stack (positive are stacked above
19623 baseValue upwards, negative are stacked below baseValue downwards). This can
19624 be indicated with \a positive. So if the bar for which we need the base value
19625 is negative, set \a positive to false.
19626 */
19627 double QCPBars::getStackedBaseValue(double key, bool positive) const {
19628 if (mBarBelow) {
19629 double max = 0; // don't use mBaseValue here because only base value of
19630 // bottom-most bar has meaning in a bar stack
19631 // find bars of mBarBelow that are approximately at key and find largest
19632 // one:
19633 double epsilon =
19634 qAbs(key) *
19635 1e-6; // should be safe even when changed to use float at some point
19636 if (key == 0) epsilon = 1e-6;
19637 QCPBarDataMap::const_iterator it =
19638 mBarBelow.data()->mData->lowerBound(key - epsilon);
19639 QCPBarDataMap::const_iterator itEnd =
19640 mBarBelow.data()->mData->upperBound(key + epsilon);
19641 while (it != itEnd) {
19642 if ((positive && it.value().value > max) ||
19643 (!positive && it.value().value < max))
19644 max = it.value().value;
19645 ++it;
19646 }
19647 // recurse down the bar-stack to find the total height:
19648 return max + mBarBelow.data()->getStackedBaseValue(key, positive);
19649 } else
19650 return mBaseValue;
19651 }
19652
19653 /*! \internal
19654
19655 Connects \a below and \a above to each other via their mBarAbove/mBarBelow
19656 properties. The bar(s) currently above lower and below upper will become
19657 disconnected to lower/upper.
19658
19659 If lower is zero, upper will be disconnected at the bottom.
19660 If upper is zero, lower will be disconnected at the top.
19661 */
19662 void QCPBars::connectBars(QCPBars *lower, QCPBars *upper) {
19663 if (!lower && !upper) return;
19664
19665 if (!lower) // disconnect upper at bottom
19666 {
19667 // disconnect old bar below upper:
19668 if (upper->mBarBelow && upper->mBarBelow.data()->mBarAbove.data() == upper)
19669 upper->mBarBelow.data()->mBarAbove = 0;
19670 upper->mBarBelow = 0;
19671 } else if (!upper) // disconnect lower at top
19672 {
19673 // disconnect old bar above lower:
19674 if (lower->mBarAbove && lower->mBarAbove.data()->mBarBelow.data() == lower)
19675 lower->mBarAbove.data()->mBarBelow = 0;
19676 lower->mBarAbove = 0;
19677 } else // connect lower and upper
19678 {
19679 // disconnect old bar above lower:
19680 if (lower->mBarAbove && lower->mBarAbove.data()->mBarBelow.data() == lower)
19681 lower->mBarAbove.data()->mBarBelow = 0;
19682 // disconnect old bar below upper:
19683 if (upper->mBarBelow && upper->mBarBelow.data()->mBarAbove.data() == upper)
19684 upper->mBarBelow.data()->mBarAbove = 0;
19685 lower->mBarAbove = upper;
19686 upper->mBarBelow = lower;
19687 }
19688 }
19689
19690 /* inherits documentation from base class */
19691 QCPRange QCPBars::getKeyRange(bool &foundRange, SignDomain inSignDomain) const {
19692 QCPRange range;
19693 bool haveLower = false;
19694 bool haveUpper = false;
19695
19696 double current;
19697 QCPBarDataMap::const_iterator it = mData->constBegin();
19698 while (it != mData->constEnd()) {
19699 current = it.value().key;
19700 if (inSignDomain == sdBoth || (inSignDomain == sdNegative && current < 0) ||
19701 (inSignDomain == sdPositive && current > 0)) {
19702 if (current < range.lower || !haveLower) {
19703 range.lower = current;
19704 haveLower = true;
19705 }
19706 if (current > range.upper || !haveUpper) {
19707 range.upper = current;
19708 haveUpper = true;
19709 }
19710 }
19711 ++it;
19712 }
19713 // determine exact range of bars by including bar width and barsgroup offset:
19714 if (haveLower && mKeyAxis) {
19715 double lowerPixelWidth, upperPixelWidth, keyPixel;
19716 getPixelWidth(range.lower, lowerPixelWidth, upperPixelWidth);
19717 keyPixel = mKeyAxis.data()->coordToPixel(range.lower) + lowerPixelWidth;
19718 if (mBarsGroup) keyPixel += mBarsGroup->keyPixelOffset(this, range.lower);
19719 range.lower = mKeyAxis.data()->pixelToCoord(keyPixel);
19720 }
19721 if (haveUpper && mKeyAxis) {
19722 double lowerPixelWidth, upperPixelWidth, keyPixel;
19723 getPixelWidth(range.upper, lowerPixelWidth, upperPixelWidth);
19724 keyPixel = mKeyAxis.data()->coordToPixel(range.upper) + upperPixelWidth;
19725 if (mBarsGroup) keyPixel += mBarsGroup->keyPixelOffset(this, range.upper);
19726 range.upper = mKeyAxis.data()->pixelToCoord(keyPixel);
19727 }
19728 foundRange = haveLower && haveUpper;
19729 return range;
19730 }
19731
19732 /* inherits documentation from base class */
19733 QCPRange QCPBars::getValueRange(bool &foundRange,
19734 SignDomain inSignDomain) const {
19735 QCPRange range;
19736 range.lower = mBaseValue;
19737 range.upper = mBaseValue;
19738 bool haveLower = true; // set to true, because baseValue should always be
19739 // visible in bar charts
19740 bool haveUpper = true; // set to true, because baseValue should always be
19741 // visible in bar charts
19742 double current;
19743
19744 QCPBarDataMap::const_iterator it = mData->constBegin();
19745 while (it != mData->constEnd()) {
19746 current = it.value().value +
19747 getStackedBaseValue(it.value().key, it.value().value >= 0);
19748 if (inSignDomain == sdBoth || (inSignDomain == sdNegative && current < 0) ||
19749 (inSignDomain == sdPositive && current > 0)) {
19750 if (current < range.lower || !haveLower) {
19751 range.lower = current;
19752 haveLower = true;
19753 }
19754 if (current > range.upper || !haveUpper) {
19755 range.upper = current;
19756 haveUpper = true;
19757 }
19758 }
19759 ++it;
19760 }
19761
19762 foundRange =
19763 true; // return true because bar charts always have the 0-line visible
19764 return range;
19765 }
19766
19767 ////////////////////////////////////////////////////////////////////////////////////////////////////
19768 //////////////////// QCPStatisticalBox
19769 ////////////////////////////////////////////////////////////////////////////////////////////////////
19770
19771 /*! \class QCPStatisticalBox
19772 \brief A plottable representing a single statistical box in a plot.
19773
19774 \image html QCPStatisticalBox.png
19775
19776 To plot data, assign it with the individual parameter functions or use \ref
19777 setData to set all parameters at once. The individual functions are: \li \ref
19778 setMinimum \li \ref setLowerQuartile \li \ref setMedian \li \ref
19779 setUpperQuartile \li \ref setMaximum
19780
19781 Additionally you can define a list of outliers, drawn as scatter datapoints:
19782 \li \ref setOutliers
19783
19784 \section appearance Changing the appearance
19785
19786 The appearance of the box itself is controlled via \ref setPen and \ref
19787 setBrush. You may change the width of the box with \ref setWidth in plot
19788 coordinates (not pixels).
19789
19790 Analog functions exist for the minimum/maximum-whiskers: \ref setWhiskerPen,
19791 \ref setWhiskerBarPen, \ref setWhiskerWidth. The whisker width is the width of
19792 the bar at the top (maximum) and bottom (minimum).
19793
19794 The median indicator line has its own pen, \ref setMedianPen.
19795
19796 If the whisker backbone pen is changed, make sure to set the capStyle to
19797 Qt::FlatCap. Else, the backbone line might exceed the whisker bars by a few
19798 pixels due to the pen cap being not perfectly flat.
19799
19800 The Outlier data points are drawn as normal scatter points. Their look can be
19801 controlled with \ref setOutlierStyle
19802
19803 \section usage Usage
19804
19805 Like all data representing objects in QCustomPlot, the QCPStatisticalBox is a
19806 plottable. Usually, you first create an instance and add it to the customPlot:
19807 \snippet documentation/doc-code-snippets/mainwindow.cpp
19808 qcpstatisticalbox-creation-1 and then modify the properties of the newly
19809 created plottable, e.g.: \snippet
19810 documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-creation-2
19811 */
19812
19813 /*!
19814 Constructs a statistical box which uses \a keyAxis as its key axis ("x") and
19815 \a valueAxis as its value axis ("y"). \a keyAxis and \a valueAxis must reside
19816 in the same QCustomPlot instance and not have the same orientation. If either
19817 of these restrictions is violated, a corresponding message is printed to the
19818 debug output (qDebug), the construction is not aborted, though.
19819
19820 The constructed statistical box can be added to the plot with
19821 QCustomPlot::addPlottable, QCustomPlot then takes ownership of the statistical
19822 box.
19823 */
19824 QCPStatisticalBox::QCPStatisticalBox(QCPAxis *keyAxis, QCPAxis *valueAxis)
19825 : QCPAbstractPlottable(keyAxis, valueAxis),
19826 mKey(0),
19827 mMinimum(0),
19828 mLowerQuartile(0),
19829 mMedian(0),
19830 mUpperQuartile(0),
19831 mMaximum(0) {
19832 setOutlierStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, Qt::blue, 6));
19833 setWhiskerWidth(0.2);
19834 setWidth(0.5);
19835
19836 setPen(QPen(Qt::black));
19837 setSelectedPen(QPen(Qt::blue, 2.5));
19838 setMedianPen(QPen(Qt::black, 3, Qt::SolidLine, Qt::FlatCap));
19839 setWhiskerPen(QPen(Qt::black, 0, Qt::DashLine, Qt::FlatCap));
19840 setWhiskerBarPen(QPen(Qt::black));
19841 setBrush(Qt::NoBrush);
19842 setSelectedBrush(Qt::NoBrush);
19843 }
19844
19845 /*!
19846 Sets the key coordinate of the statistical box.
19847 */
19848 void QCPStatisticalBox::setKey(double key) { mKey = key; }
19849
19850 /*!
19851 Sets the parameter "minimum" of the statistical box plot. This is the position
19852 of the lower whisker, typically the minimum measurement of the sample that's
19853 not considered an outlier.
19854
19855 \see setMaximum, setWhiskerPen, setWhiskerBarPen, setWhiskerWidth
19856 */
19857 void QCPStatisticalBox::setMinimum(double value) { mMinimum = value; }
19858
19859 /*!
19860 Sets the parameter "lower Quartile" of the statistical box plot. This is the
19861 lower end of the box. The lower and the upper quartiles are the two
19862 statistical quartiles around the median of the sample, they contain 50% of the
19863 sample data.
19864
19865 \see setUpperQuartile, setPen, setBrush, setWidth
19866 */
19867 void QCPStatisticalBox::setLowerQuartile(double value) {
19868 mLowerQuartile = value;
19869 }
19870
19871 /*!
19872 Sets the parameter "median" of the statistical box plot. This is the value of
19873 the median mark inside the quartile box. The median separates the sample data
19874 in half (50% of the sample data is below/above the median).
19875
19876 \see setMedianPen
19877 */
19878 void QCPStatisticalBox::setMedian(double value) { mMedian = value; }
19879
19880 /*!
19881 Sets the parameter "upper Quartile" of the statistical box plot. This is the
19882 upper end of the box. The lower and the upper quartiles are the two
19883 statistical quartiles around the median of the sample, they contain 50% of the
19884 sample data.
19885
19886 \see setLowerQuartile, setPen, setBrush, setWidth
19887 */
19888 void QCPStatisticalBox::setUpperQuartile(double value) {
19889 mUpperQuartile = value;
19890 }
19891
19892 /*!
19893 Sets the parameter "maximum" of the statistical box plot. This is the position
19894 of the upper whisker, typically the maximum measurement of the sample that's
19895 not considered an outlier.
19896
19897 \see setMinimum, setWhiskerPen, setWhiskerBarPen, setWhiskerWidth
19898 */
19899 void QCPStatisticalBox::setMaximum(double value) { mMaximum = value; }
19900
19901 /*!
19902 Sets a vector of outlier values that will be drawn as scatters. Any data
19903 points in the sample that are not within the whiskers (\ref setMinimum, \ref
19904 setMaximum) should be considered outliers and displayed as such.
19905
19906 \see setOutlierStyle
19907 */
19908 void QCPStatisticalBox::setOutliers(const QVector<double> &values) {
19909 mOutliers = values;
19910 }
19911
19912 /*!
19913 Sets all parameters of the statistical box plot at once.
19914
19915 \see setKey, setMinimum, setLowerQuartile, setMedian, setUpperQuartile,
19916 setMaximum
19917 */
19918 void QCPStatisticalBox::setData(double key, double minimum,
19919 double lowerQuartile, double median,
19920 double upperQuartile, double maximum) {
19921 setKey(key);
19922 setMinimum(minimum);
19923 setLowerQuartile(lowerQuartile);
19924 setMedian(median);
19925 setUpperQuartile(upperQuartile);
19926 setMaximum(maximum);
19927 }
19928
19929 /*!
19930 Sets the width of the box in key coordinates.
19931
19932 \see setWhiskerWidth
19933 */
19934 void QCPStatisticalBox::setWidth(double width) { mWidth = width; }
19935
19936 /*!
19937 Sets the width of the whiskers (\ref setMinimum, \ref setMaximum) in key
19938 coordinates.
19939
19940 \see setWidth
19941 */
19942 void QCPStatisticalBox::setWhiskerWidth(double width) { mWhiskerWidth = width; }
19943
19944 /*!
19945 Sets the pen used for drawing the whisker backbone (That's the line parallel
19946 to the value axis).
19947
19948 Make sure to set the \a pen capStyle to Qt::FlatCap to prevent the whisker
19949 backbone from reaching a few pixels past the whisker bars, when using a
19950 non-zero pen width.
19951
19952 \see setWhiskerBarPen
19953 */
19954 void QCPStatisticalBox::setWhiskerPen(const QPen &pen) { mWhiskerPen = pen; }
19955
19956 /*!
19957 Sets the pen used for drawing the whisker bars (Those are the lines parallel
19958 to the key axis at each end of the whisker backbone).
19959
19960 \see setWhiskerPen
19961 */
19962 void QCPStatisticalBox::setWhiskerBarPen(const QPen &pen) {
19963 mWhiskerBarPen = pen;
19964 }
19965
19966 /*!
19967 Sets the pen used for drawing the median indicator line inside the statistical
19968 box.
19969 */
19970 void QCPStatisticalBox::setMedianPen(const QPen &pen) { mMedianPen = pen; }
19971
19972 /*!
19973 Sets the appearance of the outlier data points.
19974
19975 \see setOutliers
19976 */
19977 void QCPStatisticalBox::setOutlierStyle(const QCPScatterStyle &style) {
19978 mOutlierStyle = style;
19979 }
19980
19981 /* inherits documentation from base class */
19982 void QCPStatisticalBox::clearData() {
19983 setOutliers(QVector<double>());
19984 setKey(0);
19985 setMinimum(0);
19986 setLowerQuartile(0);
19987 setMedian(0);
19988 setUpperQuartile(0);
19989 setMaximum(0);
19990 }
19991
19992 /* inherits documentation from base class */
19993 double QCPStatisticalBox::selectTest(const QPointF &pos, bool onlySelectable,
19994 QVariant *details) const {
19995 Q_UNUSED(details)
19996 if (onlySelectable && !mSelectable) return -1;
19997 if (!mKeyAxis || !mValueAxis) {
19998 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
19999 return -1;
20000 }
20001
20002 if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint())) {
20003 double posKey, posValue;
20004 pixelsToCoords(pos, posKey, posValue);
20005 // quartile box:
20006 QCPRange keyRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5);
20007 QCPRange valueRange(mLowerQuartile, mUpperQuartile);
20008 if (keyRange.contains(posKey) && valueRange.contains(posValue))
20009 return mParentPlot->selectionTolerance() * 0.99;
20010
20011 // min/max whiskers:
20012 if (QCPRange(mMinimum, mMaximum).contains(posValue))
20013 return qAbs(mKeyAxis.data()->coordToPixel(mKey) -
20014 mKeyAxis.data()->coordToPixel(posKey));
20015 }
20016 return -1;
20017 }
20018
20019 /* inherits documentation from base class */
20020 void QCPStatisticalBox::draw(QCPPainter *painter) {
20021 if (!mKeyAxis || !mValueAxis) {
20022 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
20023 return;
20024 }
20025
20026 // check data validity if flag set:
20027 #ifdef QCUSTOMPLOT_CHECK_DATA
20028 if (QCP::isInvalidData(mKey, mMedian) ||
20029 QCP::isInvalidData(mLowerQuartile, mUpperQuartile) ||
20030 QCP::isInvalidData(mMinimum, mMaximum))
20031 qDebug() << Q_FUNC_INFO << "Data point at" << mKey
20032 << "of drawn range has invalid data."
20033 << "Plottable name:" << name();
20034 for (int i = 0; i < mOutliers.size(); ++i)
20035 if (QCP::isInvalidData(mOutliers.at(i)))
20036 qDebug() << Q_FUNC_INFO << "Data point outlier at" << mKey
20037 << "of drawn range invalid."
20038 << "Plottable name:" << name();
20039 #endif
20040
20041 QRectF quartileBox;
20042 drawQuartileBox(painter, &quartileBox);
20043
20044 painter->save();
20045 painter->setClipRect(quartileBox, Qt::IntersectClip);
20046 drawMedian(painter);
20047 painter->restore();
20048
20049 drawWhiskers(painter);
20050 drawOutliers(painter);
20051 }
20052
20053 /* inherits documentation from base class */
20054 void QCPStatisticalBox::drawLegendIcon(QCPPainter *painter,
20055 const QRectF &rect) const {
20056 // draw filled rect:
20057 applyDefaultAntialiasingHint(painter);
20058 painter->setPen(mPen);
20059 painter->setBrush(mBrush);
20060 QRectF r = QRectF(0, 0, rect.width() * 0.67, rect.height() * 0.67);
20061 r.moveCenter(rect.center());
20062 painter->drawRect(r);
20063 }
20064
20065 /*! \internal
20066
20067 Draws the quartile box. \a box is an output parameter that returns the
20068 quartile box (in pixel coordinates) which is used to set the clip rect of the
20069 painter before calling \ref drawMedian (so the median doesn't draw outside the
20070 quartile box).
20071 */
20072 void QCPStatisticalBox::drawQuartileBox(QCPPainter *painter,
20073 QRectF *quartileBox) const {
20074 QRectF box;
20075 box.setTopLeft(coordsToPixels(mKey - mWidth * 0.5, mUpperQuartile));
20076 box.setBottomRight(coordsToPixels(mKey + mWidth * 0.5, mLowerQuartile));
20077 applyDefaultAntialiasingHint(painter);
20078 painter->setPen(mainPen());
20079 painter->setBrush(mainBrush());
20080 painter->drawRect(box);
20081 if (quartileBox) *quartileBox = box;
20082 }
20083
20084 /*! \internal
20085
20086 Draws the median line inside the quartile box.
20087 */
20088 void QCPStatisticalBox::drawMedian(QCPPainter *painter) const {
20089 QLineF medianLine;
20090 medianLine.setP1(coordsToPixels(mKey - mWidth * 0.5, mMedian));
20091 medianLine.setP2(coordsToPixels(mKey + mWidth * 0.5, mMedian));
20092 applyDefaultAntialiasingHint(painter);
20093 painter->setPen(mMedianPen);
20094 painter->drawLine(medianLine);
20095 }
20096
20097 /*! \internal
20098
20099 Draws both whisker backbones and bars.
20100 */
20101 void QCPStatisticalBox::drawWhiskers(QCPPainter *painter) const {
20102 QLineF backboneMin, backboneMax, barMin, barMax;
20103 backboneMax.setPoints(coordsToPixels(mKey, mUpperQuartile),
20104 coordsToPixels(mKey, mMaximum));
20105 backboneMin.setPoints(coordsToPixels(mKey, mLowerQuartile),
20106 coordsToPixels(mKey, mMinimum));
20107 barMax.setPoints(coordsToPixels(mKey - mWhiskerWidth * 0.5, mMaximum),
20108 coordsToPixels(mKey + mWhiskerWidth * 0.5, mMaximum));
20109 barMin.setPoints(coordsToPixels(mKey - mWhiskerWidth * 0.5, mMinimum),
20110 coordsToPixels(mKey + mWhiskerWidth * 0.5, mMinimum));
20111 applyErrorBarsAntialiasingHint(painter);
20112 painter->setPen(mWhiskerPen);
20113 painter->drawLine(backboneMin);
20114 painter->drawLine(backboneMax);
20115 painter->setPen(mWhiskerBarPen);
20116 painter->drawLine(barMin);
20117 painter->drawLine(barMax);
20118 }
20119
20120 /*! \internal
20121
20122 Draws the outlier scatter points.
20123 */
20124 void QCPStatisticalBox::drawOutliers(QCPPainter *painter) const {
20125 applyScattersAntialiasingHint(painter);
20126 mOutlierStyle.applyTo(painter, mPen);
20127 for (int i = 0; i < mOutliers.size(); ++i)
20128 mOutlierStyle.drawShape(painter, coordsToPixels(mKey, mOutliers.at(i)));
20129 }
20130
20131 /* inherits documentation from base class */
20132 QCPRange QCPStatisticalBox::getKeyRange(bool &foundRange,
20133 SignDomain inSignDomain) const {
20134 foundRange = true;
20135 if (inSignDomain == sdBoth) {
20136 return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5);
20137 } else if (inSignDomain == sdNegative) {
20138 if (mKey + mWidth * 0.5 < 0)
20139 return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5);
20140 else if (mKey < 0)
20141 return QCPRange(mKey - mWidth * 0.5, mKey);
20142 else {
20143 foundRange = false;
20144 return QCPRange();
20145 }
20146 } else if (inSignDomain == sdPositive) {
20147 if (mKey - mWidth * 0.5 > 0)
20148 return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5);
20149 else if (mKey > 0)
20150 return QCPRange(mKey, mKey + mWidth * 0.5);
20151 else {
20152 foundRange = false;
20153 return QCPRange();
20154 }
20155 }
20156 foundRange = false;
20157 return QCPRange();
20158 }
20159
20160 /* inherits documentation from base class */
20161 QCPRange QCPStatisticalBox::getValueRange(bool &foundRange,
20162 SignDomain inSignDomain) const {
20163 QVector<double> values; // values that must be considered (i.e. all outliers
20164 // and the five box-parameters)
20165 values.reserve(mOutliers.size() + 5);
20166 values << mMaximum << mUpperQuartile << mMedian << mLowerQuartile << mMinimum;
20167 values << mOutliers;
20168 // go through values and find the ones in legal range:
20169 bool haveUpper = false;
20170 bool haveLower = false;
20171 double upper = 0;
20172 double lower = 0;
20173 for (int i = 0; i < values.size(); ++i) {
20174 if ((inSignDomain == sdNegative && values.at(i) < 0) ||
20175 (inSignDomain == sdPositive && values.at(i) > 0) ||
20176 (inSignDomain == sdBoth)) {
20177 if (values.at(i) > upper || !haveUpper) {
20178 upper = values.at(i);
20179 haveUpper = true;
20180 }
20181 if (values.at(i) < lower || !haveLower) {
20182 lower = values.at(i);
20183 haveLower = true;
20184 }
20185 }
20186 }
20187 // return the bounds if we found some sensible values:
20188 if (haveLower && haveUpper) {
20189 foundRange = true;
20190 return QCPRange(lower, upper);
20191 } else // might happen if all values are in other sign domain
20192 {
20193 foundRange = false;
20194 return QCPRange();
20195 }
20196 }
20197
20198 ////////////////////////////////////////////////////////////////////////////////////////////////////
20199 //////////////////// QCPColorMapData
20200 ////////////////////////////////////////////////////////////////////////////////////////////////////
20201
20202 /*! \class QCPColorMapData
20203 \brief Holds the two-dimensional data of a QCPColorMap plottable.
20204
20205 This class is a data storage for \ref QCPColorMap. It holds a two-dimensional
20206 array, which \ref QCPColorMap then displays as a 2D image in the plot, where
20207 the array values are represented by a color, depending on the value.
20208
20209 The size of the array can be controlled via \ref setSize (or \ref setKeySize,
20210 \ref setValueSize). Which plot coordinates these cells correspond to can be
20211 configured with \ref setRange (or \ref setKeyRange, \ref setValueRange).
20212
20213 The data cells can be accessed in two ways: They can be directly addressed by
20214 an integer index with \ref setCell. This is the fastest method. Alternatively,
20215 they can be addressed by their plot coordinate with \ref setData. plot
20216 coordinate to cell index transformations and vice versa are provided by the
20217 functions \ref coordToCell and \ref cellToCoord.
20218
20219 This class also buffers the minimum and maximum values that are in the data
20220 set, to provide QCPColorMap::rescaleDataRange with the necessary information
20221 quickly. Setting a cell to a value that is greater than the current maximum
20222 increases this maximum to the new value. However, setting the cell that
20223 currently holds the maximum value to a smaller value doesn't decrease the
20224 maximum again, because finding the true new maximum would require going
20225 through the entire data array, which might be time consuming. The same holds
20226 for the data minimum. This functionality is given by \ref
20227 recalculateDataBounds, such that you can decide when it is sensible to find
20228 the true current minimum and maximum. The method QCPColorMap::rescaleDataRange
20229 offers a convenience parameter \a recalculateDataBounds which may be set to
20230 true to automatically call \ref recalculateDataBounds internally.
20231 */
20232
20233 /* start of documentation of inline functions */
20234
20235 /*! \fn bool QCPColorMapData::isEmpty() const
20236
20237 Returns whether this instance carries no data. This is equivalent to having a
20238 size where at least one of the dimensions is 0 (see \ref setSize).
20239 */
20240
20241 /* end of documentation of inline functions */
20242
20243 /*!
20244 Constructs a new QCPColorMapData instance. The instance has \a keySize cells
20245 in the key direction and \a valueSize cells in the value direction. These
20246 cells will be displayed by the \ref QCPColorMap at the coordinates \a keyRange
20247 and \a valueRange.
20248
20249 \see setSize, setKeySize, setValueSize, setRange, setKeyRange, setValueRange
20250 */
20251 QCPColorMapData::QCPColorMapData(int keySize, int valueSize,
20252 const QCPRange &keyRange,
20253 const QCPRange &valueRange)
20254 : mKeySize(0),
20255 mValueSize(0),
20256 mKeyRange(keyRange),
20257 mValueRange(valueRange),
20258 mIsEmpty(true),
20259 mData(0),
20260 mDataModified(true) {
20261 setSize(keySize, valueSize);
20262 fill(0);
20263 }
20264
20265 QCPColorMapData::~QCPColorMapData() {
20266 if (mData) delete[] mData;
20267 }
20268
20269 /*!
20270 Constructs a new QCPColorMapData instance copying the data and range of \a
20271 other.
20272 */
20273 QCPColorMapData::QCPColorMapData(const QCPColorMapData &other)
20274 : mKeySize(0),
20275 mValueSize(0),
20276 mIsEmpty(true),
20277 mData(0),
20278 mDataModified(true) {
20279 *this = other;
20280 }
20281
20282 /*!
20283 Overwrites this color map data instance with the data stored in \a other.
20284 */
20285 QCPColorMapData &QCPColorMapData::operator=(const QCPColorMapData &other) {
20286 if (&other != this) {
20287 const int keySize = other.keySize();
20288 const int valueSize = other.valueSize();
20289 setSize(keySize, valueSize);
20290 setRange(other.keyRange(), other.valueRange());
20291 if (!mIsEmpty)
20292 memcpy(mData, other.mData, sizeof(mData[0]) * keySize * valueSize);
20293 mDataBounds = other.mDataBounds;
20294 mDataModified = true;
20295 }
20296 return *this;
20297 }
20298
20299 /* undocumented getter */
20300 double QCPColorMapData::data(double key, double value) {
20301 int keyCell = (key - mKeyRange.lower) / (mKeyRange.upper - mKeyRange.lower) *
20302 (mKeySize - 1) +
20303 0.5;
20304 int valueCell = (value - mValueRange.lower) /
20305 (mValueRange.upper - mValueRange.lower) *
20306 (mValueSize - 1) +
20307 0.5;
20308 if (keyCell >= 0 && keyCell < mKeySize && valueCell >= 0 &&
20309 valueCell < mValueSize)
20310 return mData[valueCell * mKeySize + keyCell];
20311 else
20312 return 0;
20313 }
20314
20315 /* undocumented getter */
20316 double QCPColorMapData::cell(int keyIndex, int valueIndex) {
20317 if (keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 &&
20318 valueIndex < mValueSize)
20319 return mData[valueIndex * mKeySize + keyIndex];
20320 else
20321 return 0;
20322 }
20323
20324 /*!
20325 Resizes the data array to have \a keySize cells in the key dimension and \a
20326 valueSize cells in the value dimension.
20327
20328 The current data is discarded and the map cells are set to 0, unless the map
20329 had already the requested size.
20330
20331 Setting at least one of \a keySize or \a valueSize to zero frees the internal
20332 data array and \ref isEmpty returns true.
20333
20334 \see setRange, setKeySize, setValueSize
20335 */
20336 void QCPColorMapData::setSize(int keySize, int valueSize) {
20337 if (keySize != mKeySize || valueSize != mValueSize) {
20338 mKeySize = keySize;
20339 mValueSize = valueSize;
20340 if (mData) delete[] mData;
20341 mIsEmpty = mKeySize == 0 || mValueSize == 0;
20342 if (!mIsEmpty) {
20343 #ifdef __EXCEPTIONS
20344 try { // 2D arrays get memory intensive fast. So if the allocation fails,
20345 // at least output debug message
20346 #endif
20347 mData = new double[mKeySize * mValueSize];
20348 #ifdef __EXCEPTIONS
20349 } catch (...) {
20350 mData = 0;
20351 }
20352 #endif
20353 if (mData)
20354 fill(0);
20355 else
20356 qDebug() << Q_FUNC_INFO << "out of memory for data dimensions "
20357 << mKeySize << "*" << mValueSize;
20358 } else
20359 mData = 0;
20360 mDataModified = true;
20361 }
20362 }
20363
20364 /*!
20365 Resizes the data array to have \a keySize cells in the key dimension.
20366
20367 The current data is discarded and the map cells are set to 0, unless the map
20368 had already the requested size.
20369
20370 Setting \a keySize to zero frees the internal data array and \ref isEmpty
20371 returns true.
20372
20373 \see setKeyRange, setSize, setValueSize
20374 */
20375 void QCPColorMapData::setKeySize(int keySize) { setSize(keySize, mValueSize); }
20376
20377 /*!
20378 Resizes the data array to have \a valueSize cells in the value dimension.
20379
20380 The current data is discarded and the map cells are set to 0, unless the map
20381 had already the requested size.
20382
20383 Setting \a valueSize to zero frees the internal data array and \ref isEmpty
20384 returns true.
20385
20386 \see setValueRange, setSize, setKeySize
20387 */
20388 void QCPColorMapData::setValueSize(int valueSize) {
20389 setSize(mKeySize, valueSize);
20390 }
20391
20392 /*!
20393 Sets the coordinate ranges the data shall be distributed over. This defines
20394 the rectangular area covered by the color map in plot coordinates.
20395
20396 The outer cells will be centered on the range boundaries given to this
20397 function. For example, if the key size (\ref setKeySize) is 3 and \a keyRange
20398 is set to <tt>QCPRange(2, 3)</tt> there will be cells centered on the key
20399 coordinates 2, 2.5 and 3.
20400
20401 \see setSize
20402 */
20403 void QCPColorMapData::setRange(const QCPRange &keyRange,
20404 const QCPRange &valueRange) {
20405 setKeyRange(keyRange);
20406 setValueRange(valueRange);
20407 }
20408
20409 /*!
20410 Sets the coordinate range the data shall be distributed over in the key
20411 dimension. Together with the value range, This defines the rectangular area
20412 covered by the color map in plot coordinates.
20413
20414 The outer cells will be centered on the range boundaries given to this
20415 function. For example, if the key size (\ref setKeySize) is 3 and \a keyRange
20416 is set to <tt>QCPRange(2, 3)</tt> there will be cells centered on the key
20417 coordinates 2, 2.5 and 3.
20418
20419 \see setRange, setValueRange, setSize
20420 */
20421 void QCPColorMapData::setKeyRange(const QCPRange &keyRange) {
20422 mKeyRange = keyRange;
20423 }
20424
20425 /*!
20426 Sets the coordinate range the data shall be distributed over in the value
20427 dimension. Together with the key range, This defines the rectangular area
20428 covered by the color map in plot coordinates.
20429
20430 The outer cells will be centered on the range boundaries given to this
20431 function. For example, if the value size (\ref setValueSize) is 3 and \a
20432 valueRange is set to <tt>QCPRange(2, 3)</tt> there will be cells centered on
20433 the value coordinates 2, 2.5 and 3.
20434
20435 \see setRange, setKeyRange, setSize
20436 */
20437 void QCPColorMapData::setValueRange(const QCPRange &valueRange) {
20438 mValueRange = valueRange;
20439 }
20440
20441 /*!
20442 Sets the data of the cell, which lies at the plot coordinates given by \a key
20443 and \a value, to \a z.
20444
20445 \note The QCPColorMap always displays the data at equal key/value intervals,
20446 even if the key or value axis is set to a logarithmic scaling. If you want to
20447 use QCPColorMap with logarithmic axes, you shouldn't use the \ref
20448 QCPColorMapData::setData method as it uses a linear transformation to
20449 determine the cell index. Rather directly access the cell index with \ref
20450 QCPColorMapData::setCell.
20451
20452 \see setCell, setRange
20453 */
20454 void QCPColorMapData::setData(double key, double value, double z) {
20455 int keyCell = (key - mKeyRange.lower) / (mKeyRange.upper - mKeyRange.lower) *
20456 (mKeySize - 1) +
20457 0.5;
20458 int valueCell = (value - mValueRange.lower) /
20459 (mValueRange.upper - mValueRange.lower) *
20460 (mValueSize - 1) +
20461 0.5;
20462 if (keyCell >= 0 && keyCell < mKeySize && valueCell >= 0 &&
20463 valueCell < mValueSize) {
20464 mData[valueCell * mKeySize + keyCell] = z;
20465 if (z < mDataBounds.lower) mDataBounds.lower = z;
20466 if (z > mDataBounds.upper) mDataBounds.upper = z;
20467 mDataModified = true;
20468 }
20469 }
20470
20471 /*!
20472 Sets the data of the cell with indices \a keyIndex and \a valueIndex to \a z.
20473 The indices enumerate the cells starting from zero, up to the map's size-1 in
20474 the respective dimension (see \ref setSize).
20475
20476 In the standard plot configuration (horizontal key axis and vertical value
20477 axis, both not range-reversed), the cell with indices (0, 0) is in the bottom
20478 left corner and the cell with indices (keySize-1, valueSize-1) is in the top
20479 right corner of the color map.
20480
20481 \see setData, setSize
20482 */
20483 void QCPColorMapData::setCell(int keyIndex, int valueIndex, double z) {
20484 if (keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 &&
20485 valueIndex < mValueSize) {
20486 mData[valueIndex * mKeySize + keyIndex] = z;
20487 if (z < mDataBounds.lower) mDataBounds.lower = z;
20488 if (z > mDataBounds.upper) mDataBounds.upper = z;
20489 mDataModified = true;
20490 }
20491 }
20492
20493 /*!
20494 Goes through the data and updates the buffered minimum and maximum data
20495 values.
20496
20497 Calling this method is only advised if you are about to call \ref
20498 QCPColorMap::rescaleDataRange and can not guarantee that the cells holding the
20499 maximum or minimum data haven't been overwritten with a smaller or larger
20500 value respectively, since the buffered maximum/minimum values have been
20501 updated the last time. Why this is the case is explained in the class
20502 description (\ref QCPColorMapData).
20503
20504 Note that the method \ref QCPColorMap::rescaleDataRange provides a parameter
20505 \a recalculateDataBounds for convenience. Setting this to true will call this
20506 method for you, before doing the rescale.
20507 */
20508 void QCPColorMapData::recalculateDataBounds() {
20509 if (mKeySize > 0 && mValueSize > 0) {
20510 double minHeight = mData[0];
20511 double maxHeight = mData[0];
20512 const int dataCount = mValueSize * mKeySize;
20513 for (int i = 0; i < dataCount; ++i) {
20514 if (mData[i] > maxHeight) maxHeight = mData[i];
20515 if (mData[i] < minHeight) minHeight = mData[i];
20516 }
20517 mDataBounds.lower = minHeight;
20518 mDataBounds.upper = maxHeight;
20519 }
20520 }
20521
20522 /*!
20523 Frees the internal data memory.
20524
20525 This is equivalent to calling \ref setSize "setSize(0, 0)".
20526 */
20527 void QCPColorMapData::clear() { setSize(0, 0); }
20528
20529 /*!
20530 Sets all cells to the value \a z.
20531 */
20532 void QCPColorMapData::fill(double z) {
20533 const int dataCount = mValueSize * mKeySize;
20534 for (int i = 0; i < dataCount; ++i) mData[i] = z;
20535 mDataBounds = QCPRange(z, z);
20536 mDataModified = true;
20537 }
20538
20539 /*!
20540 Transforms plot coordinates given by \a key and \a value to cell indices of
20541 this QCPColorMapData instance. The resulting cell indices are returned via the
20542 output parameters \a keyIndex and \a valueIndex.
20543
20544 The retrieved key/value cell indices can then be used for example with \ref
20545 setCell.
20546
20547 If you are only interested in a key or value index, you may pass 0 as \a
20548 valueIndex or \a keyIndex.
20549
20550 \note The QCPColorMap always displays the data at equal key/value intervals,
20551 even if the key or value axis is set to a logarithmic scaling. If you want to
20552 use QCPColorMap with logarithmic axes, you shouldn't use the \ref
20553 QCPColorMapData::coordToCell method as it uses a linear transformation to
20554 determine the cell index.
20555
20556 \see cellToCoord, QCPAxis::coordToPixel
20557 */
20558 void QCPColorMapData::coordToCell(double key, double value, int *keyIndex,
20559 int *valueIndex) const {
20560 if (keyIndex)
20561 *keyIndex = (key - mKeyRange.lower) / (mKeyRange.upper - mKeyRange.lower) *
20562 (mKeySize - 1) +
20563 0.5;
20564 if (valueIndex)
20565 *valueIndex = (value - mValueRange.lower) /
20566 (mValueRange.upper - mValueRange.lower) *
20567 (mValueSize - 1) +
20568 0.5;
20569 }
20570
20571 /*!
20572 Transforms cell indices given by \a keyIndex and \a valueIndex to cell indices
20573 of this QCPColorMapData instance. The resulting coordinates are returned via
20574 the output parameters \a key and \a value.
20575
20576 If you are only interested in a key or value coordinate, you may pass 0 as \a
20577 key or \a value.
20578
20579 \note The QCPColorMap always displays the data at equal key/value intervals,
20580 even if the key or value axis is set to a logarithmic scaling. If you want to
20581 use QCPColorMap with logarithmic axes, you shouldn't use the \ref
20582 QCPColorMapData::cellToCoord method as it uses a linear transformation to
20583 determine the cell index.
20584
20585 \see coordToCell, QCPAxis::pixelToCoord
20586 */
20587 void QCPColorMapData::cellToCoord(int keyIndex, int valueIndex, double *key,
20588 double *value) const {
20589 if (key)
20590 *key = keyIndex / (double)(mKeySize - 1) *
20591 (mKeyRange.upper - mKeyRange.lower) +
20592 mKeyRange.lower;
20593 if (value)
20594 *value = valueIndex / (double)(mValueSize - 1) *
20595 (mValueRange.upper - mValueRange.lower) +
20596 mValueRange.lower;
20597 }
20598
20599 ////////////////////////////////////////////////////////////////////////////////////////////////////
20600 //////////////////// QCPColorMap
20601 ////////////////////////////////////////////////////////////////////////////////////////////////////
20602
20603 /*! \class QCPColorMap
20604 \brief A plottable representing a two-dimensional color map in a plot.
20605
20606 \image html QCPColorMap.png
20607
20608 The data is stored in the class \ref QCPColorMapData, which can be accessed
20609 via the data() method.
20610
20611 A color map has three dimensions to represent a data point: The \a key
20612 dimension, the \a value dimension and the \a data dimension. As with other
20613 plottables such as graphs, \a key and \a value correspond to two orthogonal
20614 axes on the QCustomPlot surface that you specify in the QCPColorMap
20615 constructor. The \a data dimension however is encoded as the color of the
20616 point at (\a key, \a value).
20617
20618 Set the number of points (or \a cells) in the key/value dimension via \ref
20619 QCPColorMapData::setSize. The plot coordinate range over which these points
20620 will be displayed is specified via \ref QCPColorMapData::setRange. The first
20621 cell will be centered on the lower range boundary and the last cell will be
20622 centered on the upper range boundary. The data can be set by either accessing
20623 the cells directly with QCPColorMapData::setCell or by addressing the cells
20624 via their plot coordinates with \ref QCPColorMapData::setData. If possible,
20625 you should prefer setCell, since it doesn't need to do any coordinate
20626 transformation and thus performs a bit better.
20627
20628 The cell with index (0, 0) is at the bottom left, if the color map uses normal
20629 (i.e. not reversed) key and value axes.
20630
20631 To show the user which colors correspond to which \a data values, a \ref
20632 QCPColorScale is typically placed to the right of the axis rect. See the
20633 documentation there for details on how to add and use a color scale.
20634
20635 \section appearance Changing the appearance
20636
20637 The central part of the appearance is the color gradient, which can be
20638 specified via \ref setGradient. See the documentation of \ref QCPColorGradient
20639 for details on configuring a color gradient.
20640
20641 The \a data range that is mapped to the colors of the gradient can be
20642 specified with \ref setDataRange. To make the data range encompass the whole
20643 data set minimum to maximum, call \ref rescaleDataRange.
20644
20645 \section usage Usage
20646
20647 Like all data representing objects in QCustomPlot, the QCPColorMap is a
20648 plottable (QCPAbstractPlottable). So the plottable-interface of QCustomPlot
20649 applies (QCustomPlot::plottable, QCustomPlot::addPlottable,
20650 QCustomPlot::removePlottable, etc.)
20651
20652 Usually, you first create an instance and add it to the customPlot:
20653 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolormap-creation-1
20654 and then modify the properties of the newly created color map, e.g.:
20655 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolormap-creation-2
20656
20657 \note The QCPColorMap always displays the data at equal key/value intervals,
20658 even if the key or value axis is set to a logarithmic scaling. If you want to
20659 use QCPColorMap with logarithmic axes, you shouldn't use the \ref
20660 QCPColorMapData::setData method as it uses a linear transformation to
20661 determine the cell index. Rather directly access the cell index with \ref
20662 QCPColorMapData::setCell.
20663 */
20664
20665 /* start documentation of inline functions */
20666
20667 /*! \fn QCPColorMapData *QCPColorMap::data() const
20668
20669 Returns a pointer to the internal data storage of type \ref QCPColorMapData.
20670 Access this to modify data points (cells) and the color map key/value range.
20671
20672 \see setData
20673 */
20674
20675 /* end documentation of inline functions */
20676
20677 /* start documentation of signals */
20678
20679 /*! \fn void QCPColorMap::dataRangeChanged(QCPRange newRange);
20680
20681 This signal is emitted when the data range changes.
20682
20683 \see setDataRange
20684 */
20685
20686 /*! \fn void QCPColorMap::dataScaleTypeChanged(QCPAxis::ScaleType scaleType);
20687
20688 This signal is emitted when the data scale type changes.
20689
20690 \see setDataScaleType
20691 */
20692
20693 /*! \fn void QCPColorMap::gradientChanged(QCPColorGradient newGradient);
20694
20695 This signal is emitted when the gradient changes.
20696
20697 \see setGradient
20698 */
20699
20700 /* end documentation of signals */
20701
20702 /*!
20703 Constructs a color map with the specified \a keyAxis and \a valueAxis.
20704
20705 The constructed QCPColorMap can be added to the plot with
20706 QCustomPlot::addPlottable, QCustomPlot then takes ownership of the color map.
20707 */
20708 QCPColorMap::QCPColorMap(QCPAxis *keyAxis, QCPAxis *valueAxis)
20709 : QCPAbstractPlottable(keyAxis, valueAxis),
20710 mDataScaleType(QCPAxis::stLinear),
20711 mMapData(new QCPColorMapData(10, 10, QCPRange(0, 5), QCPRange(0, 5))),
20712 mInterpolate(true),
20713 mTightBoundary(false),
20714 mMapImageInvalidated(true) {}
20715
20716 QCPColorMap::~QCPColorMap() { delete mMapData; }
20717
20718 /*!
20719 Replaces the current \ref data with the provided \a data.
20720
20721 If \a copy is set to true, the \a data object will only be copied. if false,
20722 the color map takes ownership of the passed data and replaces the internal
20723 data pointer with it. This is significantly faster than copying for large
20724 datasets.
20725 */
20726 void QCPColorMap::setData(QCPColorMapData *data, bool copy) {
20727 if (mMapData == data) {
20728 qDebug() << Q_FUNC_INFO
20729 << "The data pointer is already in (and owned by) this plottable"
20730 << reinterpret_cast<quintptr>(data);
20731 return;
20732 }
20733 if (copy) {
20734 *mMapData = *data;
20735 } else {
20736 delete mMapData;
20737 mMapData = data;
20738 }
20739 mMapImageInvalidated = true;
20740 }
20741
20742 /*!
20743 Sets the data range of this color map to \a dataRange. The data range defines
20744 which data values are mapped to the color gradient.
20745
20746 To make the data range span the full range of the data set, use \ref
20747 rescaleDataRange.
20748
20749 \see QCPColorScale::setDataRange
20750 */
20751 void QCPColorMap::setDataRange(const QCPRange &dataRange) {
20752 if (!QCPRange::validRange(dataRange)) return;
20753 if (mDataRange.lower != dataRange.lower ||
20754 mDataRange.upper != dataRange.upper) {
20755 if (mDataScaleType == QCPAxis::stLogarithmic)
20756 mDataRange = dataRange.sanitizedForLogScale();
20757 else
20758 mDataRange = dataRange.sanitizedForLinScale();
20759 mMapImageInvalidated = true;
20760 emit dataRangeChanged(mDataRange);
20761 }
20762 }
20763
20764 /*!
20765 Sets whether the data is correlated with the color gradient linearly or
20766 logarithmically.
20767
20768 \see QCPColorScale::setDataScaleType
20769 */
20770 void QCPColorMap::setDataScaleType(QCPAxis::ScaleType scaleType) {
20771 if (mDataScaleType != scaleType) {
20772 mDataScaleType = scaleType;
20773 mMapImageInvalidated = true;
20774 emit dataScaleTypeChanged(mDataScaleType);
20775 if (mDataScaleType == QCPAxis::stLogarithmic)
20776 setDataRange(mDataRange.sanitizedForLogScale());
20777 }
20778 }
20779
20780 /*!
20781 Sets the color gradient that is used to represent the data. For more details
20782 on how to create an own gradient or use one of the preset gradients, see \ref
20783 QCPColorGradient.
20784
20785 The colors defined by the gradient will be used to represent data values in
20786 the currently set data range, see \ref setDataRange. Data points that are
20787 outside this data range will either be colored uniformly with the respective
20788 gradient boundary color, or the gradient will repeat, depending on \ref
20789 QCPColorGradient::setPeriodic.
20790
20791 \see QCPColorScale::setGradient
20792 */
20793 void QCPColorMap::setGradient(const QCPColorGradient &gradient) {
20794 if (mGradient != gradient) {
20795 mGradient = gradient;
20796 mMapImageInvalidated = true;
20797 emit gradientChanged(mGradient);
20798 }
20799 }
20800
20801 /*!
20802 Sets whether the color map image shall use bicubic interpolation when
20803 displaying the color map shrinked or expanded, and not at a 1:1 pixel-to-data
20804 scale.
20805
20806 \image html QCPColorMap-interpolate.png "A 10*10 color map, with interpolation
20807 and without interpolation enabled"
20808 */
20809 void QCPColorMap::setInterpolate(bool enabled) {
20810 mInterpolate = enabled;
20811 mMapImageInvalidated =
20812 true; // because oversampling factors might need to change
20813 }
20814
20815 /*!
20816 Sets whether the outer most data rows and columns are clipped to the specified
20817 key and value range (see \ref QCPColorMapData::setKeyRange, \ref
20818 QCPColorMapData::setValueRange).
20819
20820 if \a enabled is set to false, the data points at the border of the color map
20821 are drawn with the same width and height as all other data points. Since the
20822 data points are represented by rectangles of one color centered on the data
20823 coordinate, this means that the shown color map extends by half a data point
20824 over the specified key/value range in each direction.
20825
20826 \image html QCPColorMap-tightboundary.png "A color map, with tight boundary
20827 enabled and disabled"
20828 */
20829 void QCPColorMap::setTightBoundary(bool enabled) { mTightBoundary = enabled; }
20830
20831 /*!
20832 Associates the color scale \a colorScale with this color map.
20833
20834 This means that both the color scale and the color map synchronize their
20835 gradient, data range and data scale type (\ref setGradient, \ref setDataRange,
20836 \ref setDataScaleType). Multiple color maps can be associated with one single
20837 color scale. This causes the color maps to also synchronize those properties,
20838 via the mutual color scale.
20839
20840 This function causes the color map to adopt the current color gradient, data
20841 range and data scale type of \a colorScale. After this call, you may change
20842 these properties at either the color map or the color scale, and the setting
20843 will be applied to both.
20844
20845 Pass 0 as \a colorScale to disconnect the color scale from this color map
20846 again.
20847 */
20848 void QCPColorMap::setColorScale(QCPColorScale *colorScale) {
20849 if (mColorScale) // unconnect signals from old color scale
20850 {
20851 disconnect(this, SIGNAL(dataRangeChanged(QCPRange)), mColorScale.data(),
20852 SLOT(setDataRange(QCPRange)));
20853 disconnect(this, SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)),
20854 mColorScale.data(), SLOT(setDataScaleType(QCPAxis::ScaleType)));
20855 disconnect(this, SIGNAL(gradientChanged(QCPColorGradient)),
20856 mColorScale.data(), SLOT(setGradient(QCPColorGradient)));
20857 disconnect(mColorScale.data(), SIGNAL(dataRangeChanged(QCPRange)), this,
20858 SLOT(setDataRange(QCPRange)));
20859 disconnect(mColorScale.data(), SIGNAL(gradientChanged(QCPColorGradient)),
20860 this, SLOT(setGradient(QCPColorGradient)));
20861 disconnect(mColorScale.data(),
20862 SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)), this,
20863 SLOT(setDataScaleType(QCPAxis::ScaleType)));
20864 }
20865 mColorScale = colorScale;
20866 if (mColorScale) // connect signals to new color scale
20867 {
20868 setGradient(mColorScale.data()->gradient());
20869 setDataRange(mColorScale.data()->dataRange());
20870 setDataScaleType(mColorScale.data()->dataScaleType());
20871 connect(this, SIGNAL(dataRangeChanged(QCPRange)), mColorScale.data(),
20872 SLOT(setDataRange(QCPRange)));
20873 connect(this, SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)),
20874 mColorScale.data(), SLOT(setDataScaleType(QCPAxis::ScaleType)));
20875 connect(this, SIGNAL(gradientChanged(QCPColorGradient)), mColorScale.data(),
20876 SLOT(setGradient(QCPColorGradient)));
20877 connect(mColorScale.data(), SIGNAL(dataRangeChanged(QCPRange)), this,
20878 SLOT(setDataRange(QCPRange)));
20879 connect(mColorScale.data(), SIGNAL(gradientChanged(QCPColorGradient)), this,
20880 SLOT(setGradient(QCPColorGradient)));
20881 connect(mColorScale.data(),
20882 SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)), this,
20883 SLOT(setDataScaleType(QCPAxis::ScaleType)));
20884 }
20885 }
20886
20887 /*!
20888 Sets the data range (\ref setDataRange) to span the minimum and maximum values
20889 that occur in the current data set. This corresponds to the \ref
20890 rescaleKeyAxis or \ref rescaleValueAxis methods, only for the third data
20891 dimension of the color map.
20892
20893 The minimum and maximum values of the data set are buffered in the internal
20894 QCPColorMapData instance (\ref data). As data is updated via its \ref
20895 QCPColorMapData::setCell or \ref QCPColorMapData::setData, the buffered
20896 minimum and maximum values are updated, too. For performance reasons, however,
20897 they are only updated in an expanding fashion. So the buffered maximum can
20898 only increase and the buffered minimum can only decrease. In consequence,
20899 changes to the data that actually lower the maximum of the data set (by
20900 overwriting the cell holding the current maximum with a smaller value), aren't
20901 recognized and the buffered maximum overestimates the true maximum of the data
20902 set. The same happens for the buffered minimum. To recalculate the true
20903 minimum and maximum by explicitly looking at each cell, the method
20904 QCPColorMapData::recalculateDataBounds can be used. For convenience, setting
20905 the parameter \a recalculateDataBounds calls this method before setting the
20906 data range to the buffered minimum and maximum.
20907
20908 \see setDataRange
20909 */
20910 void QCPColorMap::rescaleDataRange(bool recalculateDataBounds) {
20911 if (recalculateDataBounds) mMapData->recalculateDataBounds();
20912 setDataRange(mMapData->dataBounds());
20913 }
20914
20915 /*!
20916 Takes the current appearance of the color map and updates the legend icon,
20917 which is used to represent this color map in the legend (see \ref QCPLegend).
20918
20919 The \a transformMode specifies whether the rescaling is done by a faster, low
20920 quality image scaling algorithm (Qt::FastTransformation) or by a slower,
20921 higher quality algorithm (Qt::SmoothTransformation).
20922
20923 The current color map appearance is scaled down to \a thumbSize. Ideally, this
20924 should be equal to the size of the legend icon (see \ref
20925 QCPLegend::setIconSize). If it isn't exactly the configured legend icon size,
20926 the thumb will be rescaled during drawing of the legend item.
20927
20928 \see setDataRange
20929 */
20930 void QCPColorMap::updateLegendIcon(Qt::TransformationMode transformMode,
20931 const QSize &thumbSize) {
20932 if (mMapImage.isNull() && !data()->isEmpty())
20933 updateMapImage(); // try to update map image if it's null (happens if no
20934 // draw has happened yet)
20935
20936 if (!mMapImage.isNull()) // might still be null, e.g. if data is empty, so
20937 // check here again
20938 {
20939 bool mirrorX =
20940 (keyAxis()->orientation() == Qt::Horizontal ? keyAxis() : valueAxis())
20941 ->rangeReversed();
20942 bool mirrorY =
20943 (valueAxis()->orientation() == Qt::Vertical ? valueAxis() : keyAxis())
20944 ->rangeReversed();
20945 mLegendIcon = QPixmap::fromImage(mMapImage.mirrored(mirrorX, mirrorY))
20946 .scaled(thumbSize, Qt::KeepAspectRatio, transformMode);
20947 }
20948 }
20949
20950 /*!
20951 Clears the colormap data by calling \ref QCPColorMapData::clear() on the
20952 internal data. This also resizes the map to 0x0 cells.
20953 */
20954 void QCPColorMap::clearData() { mMapData->clear(); }
20955
20956 /* inherits documentation from base class */
20957 double QCPColorMap::selectTest(const QPointF &pos, bool onlySelectable,
20958 QVariant *details) const {
20959 Q_UNUSED(details)
20960 if (onlySelectable && !mSelectable) return -1;
20961 if (!mKeyAxis || !mValueAxis) {
20962 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
20963 return -1;
20964 }
20965
20966 if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint())) {
20967 double posKey, posValue;
20968 pixelsToCoords(pos, posKey, posValue);
20969 if (mMapData->keyRange().contains(posKey) &&
20970 mMapData->valueRange().contains(posValue))
20971 return mParentPlot->selectionTolerance() * 0.99;
20972 }
20973 return -1;
20974 }
20975
20976 /*! \internal
20977
20978 Updates the internal map image buffer by going through the internal \ref
20979 QCPColorMapData and turning the data values into color pixels with \ref
20980 QCPColorGradient::colorize.
20981
20982 This method is called by \ref QCPColorMap::draw if either the data has been
20983 modified or the map image has been invalidated for a different reason (e.g. a
20984 change of the data range with \ref setDataRange).
20985
20986 If the map cell count is low, the image created will be oversampled in order
20987 to avoid a QPainter::drawImage bug which makes inner pixel boundaries jitter
20988 when stretch-drawing images without smooth transform enabled. Accordingly,
20989 oversampling isn't performed if \ref setInterpolate is true.
20990 */
20991 void QCPColorMap::updateMapImage() {
20992 QCPAxis *keyAxis = mKeyAxis.data();
20993 if (!keyAxis) return;
20994 if (mMapData->isEmpty()) return;
20995
20996 const int keySize = mMapData->keySize();
20997 const int valueSize = mMapData->valueSize();
20998 int keyOversamplingFactor =
20999 mInterpolate
21000 ? 1
21001 : (int)(1.0 +
21002 100.0 /
21003 (double)keySize); // make mMapImage have at least size
21004 // 100, factor becomes 1 if size > 200
21005 // or interpolation is on
21006 int valueOversamplingFactor =
21007 mInterpolate
21008 ? 1
21009 : (int)(1.0 +
21010 100.0 /
21011 (double)valueSize); // make mMapImage have at least size
21012 // 100, factor becomes 1 if size >
21013 // 200 or interpolation is on
21014
21015 // resize mMapImage to correct dimensions including possible oversampling
21016 // factors, according to key/value axes orientation:
21017 if (keyAxis->orientation() == Qt::Horizontal &&
21018 (mMapImage.width() != keySize * keyOversamplingFactor ||
21019 mMapImage.height() != valueSize * valueOversamplingFactor))
21020 mMapImage = QImage(QSize(keySize * keyOversamplingFactor,
21021 valueSize * valueOversamplingFactor),
21022 QImage::Format_RGB32);
21023 else if (keyAxis->orientation() == Qt::Vertical &&
21024 (mMapImage.width() != valueSize * valueOversamplingFactor ||
21025 mMapImage.height() != keySize * keyOversamplingFactor))
21026 mMapImage = QImage(QSize(valueSize * valueOversamplingFactor,
21027 keySize * keyOversamplingFactor),
21028 QImage::Format_RGB32);
21029
21030 QImage *localMapImage =
21031 &mMapImage; // this is the image on which the colorization operates.
21032 // Either the final mMapImage, or if we need oversampling,
21033 // mUndersampledMapImage
21034 if (keyOversamplingFactor > 1 || valueOversamplingFactor > 1) {
21035 // resize undersampled map image to actual key/value cell sizes:
21036 if (keyAxis->orientation() == Qt::Horizontal &&
21037 (mUndersampledMapImage.width() != keySize ||
21038 mUndersampledMapImage.height() != valueSize))
21039 mUndersampledMapImage =
21040 QImage(QSize(keySize, valueSize), QImage::Format_RGB32);
21041 else if (keyAxis->orientation() == Qt::Vertical &&
21042 (mUndersampledMapImage.width() != valueSize ||
21043 mUndersampledMapImage.height() != keySize))
21044 mUndersampledMapImage =
21045 QImage(QSize(valueSize, keySize), QImage::Format_RGB32);
21046 localMapImage = &mUndersampledMapImage; // make the colorization run on the
21047 // undersampled image
21048 } else if (!mUndersampledMapImage.isNull())
21049 mUndersampledMapImage =
21050 QImage(); // don't need oversampling mechanism anymore (map size has
21051 // changed) but mUndersampledMapImage still has nonzero size,
21052 // free it
21053
21054 const double *rawData = mMapData->mData;
21055 if (keyAxis->orientation() == Qt::Horizontal) {
21056 const int lineCount = valueSize;
21057 const int rowCount = keySize;
21058 for (int line = 0; line < lineCount; ++line) {
21059 QRgb *pixels = reinterpret_cast<QRgb *>(localMapImage->scanLine(
21060 lineCount - 1 -
21061 line)); // invert scanline index because QImage counts scanlines from
21062 // top, but our vertical index counts from bottom
21063 // (mathematical coordinate system)
21064 mGradient.colorize(rawData + line * rowCount, mDataRange, pixels,
21065 rowCount, 1, mDataScaleType == QCPAxis::stLogarithmic);
21066 }
21067 } else // keyAxis->orientation() == Qt::Vertical
21068 {
21069 const int lineCount = keySize;
21070 const int rowCount = valueSize;
21071 for (int line = 0; line < lineCount; ++line) {
21072 QRgb *pixels = reinterpret_cast<QRgb *>(localMapImage->scanLine(
21073 lineCount - 1 -
21074 line)); // invert scanline index because QImage counts scanlines from
21075 // top, but our vertical index counts from bottom
21076 // (mathematical coordinate system)
21077 mGradient.colorize(rawData + line, mDataRange, pixels, rowCount,
21078 lineCount, mDataScaleType == QCPAxis::stLogarithmic);
21079 }
21080 }
21081
21082 if (keyOversamplingFactor > 1 || valueOversamplingFactor > 1) {
21083 if (keyAxis->orientation() == Qt::Horizontal)
21084 mMapImage = mUndersampledMapImage.scaled(
21085 keySize * keyOversamplingFactor, valueSize * valueOversamplingFactor,
21086 Qt::IgnoreAspectRatio, Qt::FastTransformation);
21087 else
21088 mMapImage = mUndersampledMapImage.scaled(
21089 valueSize * valueOversamplingFactor, keySize * keyOversamplingFactor,
21090 Qt::IgnoreAspectRatio, Qt::FastTransformation);
21091 }
21092 mMapData->mDataModified = false;
21093 mMapImageInvalidated = false;
21094 }
21095
21096 /* inherits documentation from base class */
21097 void QCPColorMap::draw(QCPPainter *painter) {
21098 if (mMapData->isEmpty()) return;
21099 if (!mKeyAxis || !mValueAxis) return;
21100 applyDefaultAntialiasingHint(painter);
21101
21102 if (mMapData->mDataModified || mMapImageInvalidated) updateMapImage();
21103
21104 // use buffer if painting vectorized (PDF):
21105 bool useBuffer = painter->modes().testFlag(QCPPainter::pmVectorized);
21106 QCPPainter *localPainter = painter; // will be redirected to paint on
21107 // mapBuffer if painting vectorized
21108 QRectF mapBufferTarget; // the rect in absolute widget coordinates where the
21109 // visible map portion/buffer will end up in
21110 QPixmap mapBuffer;
21111 double mapBufferPixelRatio =
21112 3; // factor by which DPI is increased in embedded bitmaps
21113 if (useBuffer) {
21114 mapBufferTarget = painter->clipRegion().boundingRect();
21115 mapBuffer =
21116 QPixmap((mapBufferTarget.size() * mapBufferPixelRatio).toSize());
21117 mapBuffer.fill(Qt::transparent);
21118 localPainter = new QCPPainter(&mapBuffer);
21119 localPainter->scale(mapBufferPixelRatio, mapBufferPixelRatio);
21120 localPainter->translate(-mapBufferTarget.topLeft());
21121 }
21122
21123 QRectF imageRect = QRectF(coordsToPixels(mMapData->keyRange().lower,
21124 mMapData->valueRange().lower),
21125 coordsToPixels(mMapData->keyRange().upper,
21126 mMapData->valueRange().upper))
21127 .normalized();
21128 // extend imageRect to contain outer halves/quarters of bordering/cornering
21129 // pixels (cells are centered on map range boundary):
21130 double halfCellWidth = 0; // in pixels
21131 double halfCellHeight = 0; // in pixels
21132 if (keyAxis()->orientation() == Qt::Horizontal) {
21133 if (mMapData->keySize() > 1)
21134 halfCellWidth =
21135 0.5 * imageRect.width() / (double)(mMapData->keySize() - 1);
21136 if (mMapData->valueSize() > 1)
21137 halfCellHeight =
21138 0.5 * imageRect.height() / (double)(mMapData->valueSize() - 1);
21139 } else // keyAxis orientation is Qt::Vertical
21140 {
21141 if (mMapData->keySize() > 1)
21142 halfCellHeight =
21143 0.5 * imageRect.height() / (double)(mMapData->keySize() - 1);
21144 if (mMapData->valueSize() > 1)
21145 halfCellWidth =
21146 0.5 * imageRect.width() / (double)(mMapData->valueSize() - 1);
21147 }
21148 imageRect.adjust(-halfCellWidth, -halfCellHeight, halfCellWidth,
21149 halfCellHeight);
21150 bool mirrorX =
21151 (keyAxis()->orientation() == Qt::Horizontal ? keyAxis() : valueAxis())
21152 ->rangeReversed();
21153 bool mirrorY =
21154 (valueAxis()->orientation() == Qt::Vertical ? valueAxis() : keyAxis())
21155 ->rangeReversed();
21156 bool smoothBackup =
21157 localPainter->renderHints().testFlag(QPainter::SmoothPixmapTransform);
21158 localPainter->setRenderHint(QPainter::SmoothPixmapTransform, mInterpolate);
21159 QRegion clipBackup;
21160 if (mTightBoundary) {
21161 clipBackup = localPainter->clipRegion();
21162 QRectF tightClipRect = QRectF(coordsToPixels(mMapData->keyRange().lower,
21163 mMapData->valueRange().lower),
21164 coordsToPixels(mMapData->keyRange().upper,
21165 mMapData->valueRange().upper))
21166 .normalized();
21167 localPainter->setClipRect(tightClipRect, Qt::IntersectClip);
21168 }
21169 localPainter->drawImage(imageRect, mMapImage.mirrored(mirrorX, mirrorY));
21170 if (mTightBoundary) localPainter->setClipRegion(clipBackup);
21171 localPainter->setRenderHint(QPainter::SmoothPixmapTransform, smoothBackup);
21172
21173 if (useBuffer) // localPainter painted to mapBuffer, so now draw buffer with
21174 // original painter
21175 {
21176 delete localPainter;
21177 painter->drawPixmap(mapBufferTarget.toRect(), mapBuffer);
21178 }
21179 }
21180
21181 /* inherits documentation from base class */
21182 void QCPColorMap::drawLegendIcon(QCPPainter *painter,
21183 const QRectF &rect) const {
21184 applyDefaultAntialiasingHint(painter);
21185 // draw map thumbnail:
21186 if (!mLegendIcon.isNull()) {
21187 QPixmap scaledIcon = mLegendIcon.scaled(
21188 rect.size().toSize(), Qt::KeepAspectRatio, Qt::FastTransformation);
21189 QRectF iconRect = QRectF(0, 0, scaledIcon.width(), scaledIcon.height());
21190 iconRect.moveCenter(rect.center());
21191 painter->drawPixmap(iconRect.topLeft(), scaledIcon);
21192 }
21193 /*
21194 // draw frame:
21195 painter->setBrush(Qt::NoBrush);
21196 painter->setPen(Qt::black);
21197 painter->drawRect(rect.adjusted(1, 1, 0, 0));
21198 */
21199 }
21200
21201 /* inherits documentation from base class */
21202 QCPRange QCPColorMap::getKeyRange(bool &foundRange,
21203 SignDomain inSignDomain) const {
21204 foundRange = true;
21205 QCPRange result = mMapData->keyRange();
21206 result.normalize();
21207 if (inSignDomain == QCPAbstractPlottable::sdPositive) {
21208 if (result.lower <= 0 && result.upper > 0)
21209 result.lower = result.upper * 1e-3;
21210 else if (result.lower <= 0 && result.upper <= 0)
21211 foundRange = false;
21212 } else if (inSignDomain == QCPAbstractPlottable::sdNegative) {
21213 if (result.upper >= 0 && result.lower < 0)
21214 result.upper = result.lower * 1e-3;
21215 else if (result.upper >= 0 && result.lower >= 0)
21216 foundRange = false;
21217 }
21218 return result;
21219 }
21220
21221 /* inherits documentation from base class */
21222 QCPRange QCPColorMap::getValueRange(bool &foundRange,
21223 SignDomain inSignDomain) const {
21224 foundRange = true;
21225 QCPRange result = mMapData->valueRange();
21226 result.normalize();
21227 if (inSignDomain == QCPAbstractPlottable::sdPositive) {
21228 if (result.lower <= 0 && result.upper > 0)
21229 result.lower = result.upper * 1e-3;
21230 else if (result.lower <= 0 && result.upper <= 0)
21231 foundRange = false;
21232 } else if (inSignDomain == QCPAbstractPlottable::sdNegative) {
21233 if (result.upper >= 0 && result.lower < 0)
21234 result.upper = result.lower * 1e-3;
21235 else if (result.upper >= 0 && result.lower >= 0)
21236 foundRange = false;
21237 }
21238 return result;
21239 }
21240
21241 ////////////////////////////////////////////////////////////////////////////////////////////////////
21242 //////////////////// QCPFinancialData
21243 ////////////////////////////////////////////////////////////////////////////////////////////////////
21244
21245 /*! \class QCPFinancialData
21246 \brief Holds the data of one single data point for QCPFinancial.
21247
21248 The container for storing multiple data points is \ref QCPFinancialDataMap.
21249
21250 The stored data is:
21251 \li \a key: coordinate on the key axis of this data point
21252 \li \a open: The opening value at the data point
21253 \li \a high: The high/maximum value at the data point
21254 \li \a low: The low/minimum value at the data point
21255 \li \a close: The closing value at the data point
21256
21257 \see QCPFinancialDataMap
21258 */
21259
21260 /*!
21261 Constructs a data point with key and all values set to zero.
21262 */
21263 QCPFinancialData::QCPFinancialData()
21264 : key(0), open(0), high(0), low(0), close(0) {}
21265
21266 /*!
21267 Constructs a data point with the specified \a key and OHLC values.
21268 */
21269 QCPFinancialData::QCPFinancialData(double key, double open, double high,
21270 double low, double close)
21271 : key(key), open(open), high(high), low(low), close(close) {}
21272
21273 ////////////////////////////////////////////////////////////////////////////////////////////////////
21274 //////////////////// QCPFinancial
21275 ////////////////////////////////////////////////////////////////////////////////////////////////////
21276
21277 /*! \class QCPFinancial
21278 \brief A plottable representing a financial stock chart
21279
21280 \image html QCPFinancial.png
21281
21282 This plottable represents time series data binned to certain intervals, mainly
21283 used for stock charts. The two common representations OHLC
21284 (Open-High-Low-Close) bars and Candlesticks can be set via \ref setChartStyle.
21285
21286 The data is passed via \ref setData as a set of open/high/low/close values at
21287 certain keys (typically times). This means the data must be already binned
21288 appropriately. If data is only available as a series of values (e.g. \a price
21289 against \a time), you can use the static convenience function \ref
21290 timeSeriesToOhlc to generate binned OHLC-data which can then be passed to \ref
21291 setData.
21292
21293 The width of the OHLC bars/candlesticks can be controlled with \ref setWidth
21294 and is given in plot key coordinates. A typical choice is to set it to (or
21295 slightly less than) one bin interval width.
21296
21297 \section appearance Changing the appearance
21298
21299 Charts can be either single- or two-colored (\ref setTwoColored). If set to be
21300 single-colored, lines are drawn with the plottable's pen (\ref setPen) and
21301 fills with the brush (\ref setBrush).
21302
21303 If set to two-colored, positive changes of the value during an interval (\a
21304 close >= \a open) are represented with a different pen and brush than negative
21305 changes (\a close < \a open). These can be configured with \ref
21306 setPenPositive, \ref setPenNegative, \ref setBrushPositive, and \ref
21307 setBrushNegative. In two-colored mode, the normal plottable pen/brush is
21308 ignored. Upon selection however, the normal selected pen/brush (\ref
21309 setSelectedPen, \ref setSelectedBrush) is used, irrespective of whether the
21310 chart is single- or two-colored.
21311
21312 */
21313
21314 /* start of documentation of inline functions */
21315
21316 /*! \fn QCPFinancialDataMap *QCPFinancial::data() const
21317
21318 Returns a pointer to the internal data storage of type \ref
21319 QCPFinancialDataMap. You may use it to directly manipulate the data, which may
21320 be more convenient and faster than using the regular \ref setData or \ref
21321 addData methods, in certain situations.
21322 */
21323
21324 /* end of documentation of inline functions */
21325
21326 /*!
21327 Constructs a financial chart which uses \a keyAxis as its key axis ("x") and
21328 \a valueAxis as its value axis ("y"). \a keyAxis and \a valueAxis must reside
21329 in the same QCustomPlot instance and not have the same orientation. If either
21330 of these restrictions is violated, a corresponding message is printed to the
21331 debug output (qDebug), the construction is not aborted, though.
21332
21333 The constructed QCPFinancial can be added to the plot with
21334 QCustomPlot::addPlottable, QCustomPlot then takes ownership of the financial
21335 chart.
21336 */
21337 QCPFinancial::QCPFinancial(QCPAxis *keyAxis, QCPAxis *valueAxis)
21338 : QCPAbstractPlottable(keyAxis, valueAxis),
21339 mData(0),
21340 mChartStyle(csOhlc),
21341 mWidth(0.5),
21342 mTwoColored(false),
21343 mBrushPositive(QBrush(QColor(210, 210, 255))),
21344 mBrushNegative(QBrush(QColor(255, 210, 210))),
21345 mPenPositive(QPen(QColor(10, 40, 180))),
21346 mPenNegative(QPen(QColor(180, 40, 10))) {
21347 mData = new QCPFinancialDataMap;
21348
21349 setSelectedPen(QPen(QColor(80, 80, 255), 2.5));
21350 setSelectedBrush(QBrush(QColor(80, 80, 255)));
21351 }
21352
21353 QCPFinancial::~QCPFinancial() { delete mData; }
21354
21355 /*!
21356 Replaces the current data with the provided \a data.
21357
21358 If \a copy is set to true, data points in \a data will only be copied. if
21359 false, the plottable takes ownership of the passed data and replaces the
21360 internal data pointer with it. This is significantly faster than copying for
21361 large datasets.
21362
21363 Alternatively, you can also access and modify the plottable's data via the
21364 \ref data method, which returns a pointer to the internal \ref
21365 QCPFinancialDataMap.
21366
21367 \see timeSeriesToOhlc
21368 */
21369 void QCPFinancial::setData(QCPFinancialDataMap *data, bool copy) {
21370 if (mData == data) {
21371 qDebug() << Q_FUNC_INFO
21372 << "The data pointer is already in (and owned by) this plottable"
21373 << reinterpret_cast<quintptr>(data);
21374 return;
21375 }
21376 if (copy) {
21377 *mData = *data;
21378 } else {
21379 delete mData;
21380 mData = data;
21381 }
21382 }
21383
21384 /*! \overload
21385
21386 Replaces the current data with the provided open/high/low/close data. The
21387 provided vectors should have equal length. Else, the number of added points
21388 will be the size of the smallest vector.
21389
21390 \see timeSeriesToOhlc
21391 */
21392 void QCPFinancial::setData(const QVector<double> &key,
21393 const QVector<double> &open,
21394 const QVector<double> &high,
21395 const QVector<double> &low,
21396 const QVector<double> &close) {
21397 mData->clear();
21398 int n = key.size();
21399 n = qMin(n, open.size());
21400 n = qMin(n, high.size());
21401 n = qMin(n, low.size());
21402 n = qMin(n, close.size());
21403 for (int i = 0; i < n; ++i) {
21404 mData->insertMulti(
21405 key[i], QCPFinancialData(key[i], open[i], high[i], low[i], close[i]));
21406 }
21407 }
21408
21409 /*!
21410 Sets which representation style shall be used to display the OHLC data.
21411 */
21412 void QCPFinancial::setChartStyle(QCPFinancial::ChartStyle style) {
21413 mChartStyle = style;
21414 }
21415
21416 /*!
21417 Sets the width of the individual bars/candlesticks to \a width in plot key
21418 coordinates.
21419
21420 A typical choice is to set it to (or slightly less than) one bin interval
21421 width.
21422 */
21423 void QCPFinancial::setWidth(double width) { mWidth = width; }
21424
21425 /*!
21426 Sets whether this chart shall contrast positive from negative trends per data
21427 point by using two separate colors to draw the respective bars/candlesticks.
21428
21429 If \a twoColored is false, the normal plottable's pen and brush are used (\ref
21430 setPen, \ref setBrush).
21431
21432 \see setPenPositive, setPenNegative, setBrushPositive, setBrushNegative
21433 */
21434 void QCPFinancial::setTwoColored(bool twoColored) { mTwoColored = twoColored; }
21435
21436 /*!
21437 If \ref setTwoColored is set to true, this function controls the brush that is
21438 used to draw fills of data points with a positive trend (i.e.
21439 bars/candlesticks with close >= open).
21440
21441 If \a twoColored is false, the normal plottable's pen and brush are used (\ref
21442 setPen, \ref setBrush).
21443
21444 \see setBrushNegative, setPenPositive, setPenNegative
21445 */
21446 void QCPFinancial::setBrushPositive(const QBrush &brush) {
21447 mBrushPositive = brush;
21448 }
21449
21450 /*!
21451 If \ref setTwoColored is set to true, this function controls the brush that is
21452 used to draw fills of data points with a negative trend (i.e.
21453 bars/candlesticks with close < open).
21454
21455 If \a twoColored is false, the normal plottable's pen and brush are used (\ref
21456 setPen, \ref setBrush).
21457
21458 \see setBrushPositive, setPenNegative, setPenPositive
21459 */
21460 void QCPFinancial::setBrushNegative(const QBrush &brush) {
21461 mBrushNegative = brush;
21462 }
21463
21464 /*!
21465 If \ref setTwoColored is set to true, this function controls the pen that is
21466 used to draw outlines of data points with a positive trend (i.e.
21467 bars/candlesticks with close >= open).
21468
21469 If \a twoColored is false, the normal plottable's pen and brush are used (\ref
21470 setPen, \ref setBrush).
21471
21472 \see setPenNegative, setBrushPositive, setBrushNegative
21473 */
21474 void QCPFinancial::setPenPositive(const QPen &pen) { mPenPositive = pen; }
21475
21476 /*!
21477 If \ref setTwoColored is set to true, this function controls the pen that is
21478 used to draw outlines of data points with a negative trend (i.e.
21479 bars/candlesticks with close < open).
21480
21481 If \a twoColored is false, the normal plottable's pen and brush are used (\ref
21482 setPen, \ref setBrush).
21483
21484 \see setPenPositive, setBrushNegative, setBrushPositive
21485 */
21486 void QCPFinancial::setPenNegative(const QPen &pen) { mPenNegative = pen; }
21487
21488 /*!
21489 Adds the provided data points in \a dataMap to the current data.
21490
21491 Alternatively, you can also access and modify the data via the \ref data
21492 method, which returns a pointer to the internal \ref QCPFinancialDataMap.
21493
21494 \see removeData
21495 */
21496 void QCPFinancial::addData(const QCPFinancialDataMap &dataMap) {
21497 mData->unite(dataMap);
21498 }
21499
21500 /*! \overload
21501
21502 Adds the provided single data point in \a data to the current data.
21503
21504 Alternatively, you can also access and modify the data via the \ref data
21505 method, which returns a pointer to the internal \ref QCPFinancialData.
21506
21507 \see removeData
21508 */
21509 void QCPFinancial::addData(const QCPFinancialData &data) {
21510 mData->insertMulti(data.key, data);
21511 }
21512
21513 /*! \overload
21514
21515 Adds the provided single data point given by \a key, \a open, \a high, \a low,
21516 and \a close to the current data.
21517
21518 Alternatively, you can also access and modify the data via the \ref data
21519 method, which returns a pointer to the internal \ref QCPFinancialData.
21520
21521 \see removeData
21522 */
21523 void QCPFinancial::addData(double key, double open, double high, double low,
21524 double close) {
21525 mData->insertMulti(key, QCPFinancialData(key, open, high, low, close));
21526 }
21527
21528 /*! \overload
21529
21530 Adds the provided open/high/low/close data to the current data.
21531
21532 Alternatively, you can also access and modify the data via the \ref data
21533 method, which returns a pointer to the internal \ref QCPFinancialData.
21534
21535 \see removeData
21536 */
21537 void QCPFinancial::addData(const QVector<double> &key,
21538 const QVector<double> &open,
21539 const QVector<double> &high,
21540 const QVector<double> &low,
21541 const QVector<double> &close) {
21542 int n = key.size();
21543 n = qMin(n, open.size());
21544 n = qMin(n, high.size());
21545 n = qMin(n, low.size());
21546 n = qMin(n, close.size());
21547 for (int i = 0; i < n; ++i) {
21548 mData->insertMulti(
21549 key[i], QCPFinancialData(key[i], open[i], high[i], low[i], close[i]));
21550 }
21551 }
21552
21553 /*!
21554 Removes all data points with keys smaller than \a key.
21555
21556 \see addData, clearData
21557 */
21558 void QCPFinancial::removeDataBefore(double key) {
21559 QCPFinancialDataMap::iterator it = mData->begin();
21560 while (it != mData->end() && it.key() < key) it = mData->erase(it);
21561 }
21562
21563 /*!
21564 Removes all data points with keys greater than \a key.
21565
21566 \see addData, clearData
21567 */
21568 void QCPFinancial::removeDataAfter(double key) {
21569 if (mData->isEmpty()) return;
21570 QCPFinancialDataMap::iterator it = mData->upperBound(key);
21571 while (it != mData->end()) it = mData->erase(it);
21572 }
21573
21574 /*!
21575 Removes all data points with keys between \a fromKey and \a toKey. if \a
21576 fromKey is greater or equal to \a toKey, the function does nothing. To remove
21577 a single data point with known key, use \ref removeData(double key).
21578
21579 \see addData, clearData
21580 */
21581 void QCPFinancial::removeData(double fromKey, double toKey) {
21582 if (fromKey >= toKey || mData->isEmpty()) return;
21583 QCPFinancialDataMap::iterator it = mData->upperBound(fromKey);
21584 QCPFinancialDataMap::iterator itEnd = mData->upperBound(toKey);
21585 while (it != itEnd) it = mData->erase(it);
21586 }
21587
21588 /*! \overload
21589
21590 Removes a single data point at \a key. If the position is not known with
21591 absolute precision, consider using \ref removeData(double fromKey, double
21592 toKey) with a small fuzziness interval around the suspected position, depeding
21593 on the precision with which the key is known.
21594
21595 \see addData, clearData
21596 */
21597 void QCPFinancial::removeData(double key) { mData->remove(key); }
21598
21599 /*!
21600 Removes all data points.
21601
21602 \see removeData, removeDataAfter, removeDataBefore
21603 */
21604 void QCPFinancial::clearData() { mData->clear(); }
21605
21606 /* inherits documentation from base class */
21607 double QCPFinancial::selectTest(const QPointF &pos, bool onlySelectable,
21608 QVariant *details) const {
21609 Q_UNUSED(details)
21610 if (onlySelectable && !mSelectable) return -1;
21611 if (!mKeyAxis || !mValueAxis) {
21612 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
21613 return -1;
21614 }
21615
21616 if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint())) {
21617 // get visible data range:
21618 QCPFinancialDataMap::const_iterator lower,
21619 upper; // note that upper is the actual upper point, and not 1 step
21620 // after the upper point
21621 getVisibleDataBounds(lower, upper);
21622 if (lower == mData->constEnd() || upper == mData->constEnd()) return -1;
21623 // perform select test according to configured style:
21624 switch (mChartStyle) {
21625 case QCPFinancial::csOhlc:
21626 return ohlcSelectTest(pos, lower, upper + 1);
21627 break;
21628 case QCPFinancial::csCandlestick:
21629 return candlestickSelectTest(pos, lower, upper + 1);
21630 break;
21631 }
21632 }
21633 return -1;
21634 }
21635
21636 /*!
21637 A convenience function that converts time series data (\a value against \a
21638 time) to OHLC binned data points. The return value can then be passed on to
21639 \ref setData.
21640
21641 The size of the bins can be controlled with \a timeBinSize in the same units
21642 as \a time is given. For example, if the unit of \a time is seconds and single
21643 OHLC/Candlesticks should span an hour each, set \a timeBinSize to 3600.
21644
21645 \a timeBinOffset allows to control precisely at what \a time coordinate a bin
21646 should start. The value passed as \a timeBinOffset doesn't need to be in the
21647 range encompassed by the \a time keys. It merely defines the mathematical
21648 offset/phase of the bins that will be used to process the data.
21649 */
21650 QCPFinancialDataMap QCPFinancial::timeSeriesToOhlc(const QVector<double> &time,
21651 const QVector<double> &value,
21652 double timeBinSize,
21653 double timeBinOffset) {
21654 QCPFinancialDataMap map;
21655 int count = qMin(time.size(), value.size());
21656 if (count == 0) return QCPFinancialDataMap();
21657
21658 QCPFinancialData currentBinData(0, value.first(), value.first(),
21659 value.first(), value.first());
21660 int currentBinIndex =
21661 qFloor((time.first() - timeBinOffset) / timeBinSize + 0.5);
21662 for (int i = 0; i < count; ++i) {
21663 int index = qFloor((time.at(i) - timeBinOffset) / timeBinSize + 0.5);
21664 if (currentBinIndex ==
21665 index) // data point still in current bin, extend high/low:
21666 {
21667 if (value.at(i) < currentBinData.low) currentBinData.low = value.at(i);
21668 if (value.at(i) > currentBinData.high) currentBinData.high = value.at(i);
21669 if (i == count - 1) // last data point is in current bin, finalize bin:
21670 {
21671 currentBinData.close = value.at(i);
21672 currentBinData.key = timeBinOffset + (index)*timeBinSize;
21673 map.insert(currentBinData.key, currentBinData);
21674 }
21675 } else // data point not anymore in current bin, set close of old and open
21676 // of new bin, and add old to map:
21677 {
21678 // finalize current bin:
21679 currentBinData.close = value.at(i - 1);
21680 currentBinData.key = timeBinOffset + (index - 1) * timeBinSize;
21681 map.insert(currentBinData.key, currentBinData);
21682 // start next bin:
21683 currentBinIndex = index;
21684 currentBinData.open = value.at(i);
21685 currentBinData.high = value.at(i);
21686 currentBinData.low = value.at(i);
21687 }
21688 }
21689
21690 return map;
21691 }
21692
21693 /* inherits documentation from base class */
21694 void QCPFinancial::draw(QCPPainter *painter) {
21695 // get visible data range:
21696 QCPFinancialDataMap::const_iterator lower,
21697 upper; // note that upper is the actual upper point, and not 1 step after
21698 // the upper point
21699 getVisibleDataBounds(lower, upper);
21700 if (lower == mData->constEnd() || upper == mData->constEnd()) return;
21701
21702 // draw visible data range according to configured style:
21703 switch (mChartStyle) {
21704 case QCPFinancial::csOhlc:
21705 drawOhlcPlot(painter, lower, upper + 1);
21706 break;
21707 case QCPFinancial::csCandlestick:
21708 drawCandlestickPlot(painter, lower, upper + 1);
21709 break;
21710 }
21711 }
21712
21713 /* inherits documentation from base class */
21714 void QCPFinancial::drawLegendIcon(QCPPainter *painter,
21715 const QRectF &rect) const {
21716 painter->setAntialiasing(false); // legend icon especially of csCandlestick
21717 // looks better without antialiasing
21718 if (mChartStyle == csOhlc) {
21719 if (mTwoColored) {
21720 // draw upper left half icon with positive color:
21721 painter->setBrush(mBrushPositive);
21722 painter->setPen(mPenPositive);
21723 painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint()
21724 << rect.topRight().toPoint()
21725 << rect.topLeft().toPoint()));
21726 painter->drawLine(
21727 QLineF(0, rect.height() * 0.5, rect.width(), rect.height() * 0.5)
21728 .translated(rect.topLeft()));
21729 painter->drawLine(QLineF(rect.width() * 0.2, rect.height() * 0.3,
21730 rect.width() * 0.2, rect.height() * 0.5)
21731 .translated(rect.topLeft()));
21732 painter->drawLine(QLineF(rect.width() * 0.8, rect.height() * 0.5,
21733 rect.width() * 0.8, rect.height() * 0.7)
21734 .translated(rect.topLeft()));
21735 // draw bottom right hald icon with negative color:
21736 painter->setBrush(mBrushNegative);
21737 painter->setPen(mPenNegative);
21738 painter->setClipRegion(QRegion(
21739 QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint()
21740 << rect.bottomRight().toPoint()));
21741 painter->drawLine(
21742 QLineF(0, rect.height() * 0.5, rect.width(), rect.height() * 0.5)
21743 .translated(rect.topLeft()));
21744 painter->drawLine(QLineF(rect.width() * 0.2, rect.height() * 0.3,
21745 rect.width() * 0.2, rect.height() * 0.5)
21746 .translated(rect.topLeft()));
21747 painter->drawLine(QLineF(rect.width() * 0.8, rect.height() * 0.5,
21748 rect.width() * 0.8, rect.height() * 0.7)
21749 .translated(rect.topLeft()));
21750 } else {
21751 painter->setBrush(mBrush);
21752 painter->setPen(mPen);
21753 painter->drawLine(
21754 QLineF(0, rect.height() * 0.5, rect.width(), rect.height() * 0.5)
21755 .translated(rect.topLeft()));
21756 painter->drawLine(QLineF(rect.width() * 0.2, rect.height() * 0.3,
21757 rect.width() * 0.2, rect.height() * 0.5)
21758 .translated(rect.topLeft()));
21759 painter->drawLine(QLineF(rect.width() * 0.8, rect.height() * 0.5,
21760 rect.width() * 0.8, rect.height() * 0.7)
21761 .translated(rect.topLeft()));
21762 }
21763 } else if (mChartStyle == csCandlestick) {
21764 if (mTwoColored) {
21765 // draw upper left half icon with positive color:
21766 painter->setBrush(mBrushPositive);
21767 painter->setPen(mPenPositive);
21768 painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint()
21769 << rect.topRight().toPoint()
21770 << rect.topLeft().toPoint()));
21771 painter->drawLine(QLineF(0, rect.height() * 0.5, rect.width() * 0.25,
21772 rect.height() * 0.5)
21773 .translated(rect.topLeft()));
21774 painter->drawLine(QLineF(rect.width() * 0.75, rect.height() * 0.5,
21775 rect.width(), rect.height() * 0.5)
21776 .translated(rect.topLeft()));
21777 painter->drawRect(QRectF(rect.width() * 0.25, rect.height() * 0.25,
21778 rect.width() * 0.5, rect.height() * 0.5)
21779 .translated(rect.topLeft()));
21780 // draw bottom right hald icon with negative color:
21781 painter->setBrush(mBrushNegative);
21782 painter->setPen(mPenNegative);
21783 painter->setClipRegion(QRegion(
21784 QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint()
21785 << rect.bottomRight().toPoint()));
21786 painter->drawLine(QLineF(0, rect.height() * 0.5, rect.width() * 0.25,
21787 rect.height() * 0.5)
21788 .translated(rect.topLeft()));
21789 painter->drawLine(QLineF(rect.width() * 0.75, rect.height() * 0.5,
21790 rect.width(), rect.height() * 0.5)
21791 .translated(rect.topLeft()));
21792 painter->drawRect(QRectF(rect.width() * 0.25, rect.height() * 0.25,
21793 rect.width() * 0.5, rect.height() * 0.5)
21794 .translated(rect.topLeft()));
21795 } else {
21796 painter->setBrush(mBrush);
21797 painter->setPen(mPen);
21798 painter->drawLine(QLineF(0, rect.height() * 0.5, rect.width() * 0.25,
21799 rect.height() * 0.5)
21800 .translated(rect.topLeft()));
21801 painter->drawLine(QLineF(rect.width() * 0.75, rect.height() * 0.5,
21802 rect.width(), rect.height() * 0.5)
21803 .translated(rect.topLeft()));
21804 painter->drawRect(QRectF(rect.width() * 0.25, rect.height() * 0.25,
21805 rect.width() * 0.5, rect.height() * 0.5)
21806 .translated(rect.topLeft()));
21807 }
21808 }
21809 }
21810
21811 /* inherits documentation from base class */
21812 QCPRange QCPFinancial::getKeyRange(
21813 bool &foundRange, QCPAbstractPlottable::SignDomain inSignDomain) const {
21814 QCPRange range;
21815 bool haveLower = false;
21816 bool haveUpper = false;
21817
21818 double current;
21819 QCPFinancialDataMap::const_iterator it = mData->constBegin();
21820 while (it != mData->constEnd()) {
21821 current = it.value().key;
21822 if (inSignDomain == sdBoth || (inSignDomain == sdNegative && current < 0) ||
21823 (inSignDomain == sdPositive && current > 0)) {
21824 if (current < range.lower || !haveLower) {
21825 range.lower = current;
21826 haveLower = true;
21827 }
21828 if (current > range.upper || !haveUpper) {
21829 range.upper = current;
21830 haveUpper = true;
21831 }
21832 }
21833 ++it;
21834 }
21835 // determine exact range by including width of bars/flags:
21836 if (haveLower && mKeyAxis) range.lower = range.lower - mWidth * 0.5;
21837 if (haveUpper && mKeyAxis) range.upper = range.upper + mWidth * 0.5;
21838 foundRange = haveLower && haveUpper;
21839 return range;
21840 }
21841
21842 /* inherits documentation from base class */
21843 QCPRange QCPFinancial::getValueRange(
21844 bool &foundRange, QCPAbstractPlottable::SignDomain inSignDomain) const {
21845 QCPRange range;
21846 bool haveLower = false;
21847 bool haveUpper = false;
21848
21849 QCPFinancialDataMap::const_iterator it = mData->constBegin();
21850 while (it != mData->constEnd()) {
21851 // high:
21852 if (inSignDomain == sdBoth ||
21853 (inSignDomain == sdNegative && it.value().high < 0) ||
21854 (inSignDomain == sdPositive && it.value().high > 0)) {
21855 if (it.value().high < range.lower || !haveLower) {
21856 range.lower = it.value().high;
21857 haveLower = true;
21858 }
21859 if (it.value().high > range.upper || !haveUpper) {
21860 range.upper = it.value().high;
21861 haveUpper = true;
21862 }
21863 }
21864 // low:
21865 if (inSignDomain == sdBoth ||
21866 (inSignDomain == sdNegative && it.value().low < 0) ||
21867 (inSignDomain == sdPositive && it.value().low > 0)) {
21868 if (it.value().low < range.lower || !haveLower) {
21869 range.lower = it.value().low;
21870 haveLower = true;
21871 }
21872 if (it.value().low > range.upper || !haveUpper) {
21873 range.upper = it.value().low;
21874 haveUpper = true;
21875 }
21876 }
21877 ++it;
21878 }
21879
21880 foundRange = haveLower && haveUpper;
21881 return range;
21882 }
21883
21884 /*! \internal
21885
21886 Draws the data from \a begin to \a end as OHLC bars with the provided \a
21887 painter.
21888
21889 This method is a helper function for \ref draw. It is used when the chart
21890 style is \ref csOhlc.
21891 */
21892 void QCPFinancial::drawOhlcPlot(
21893 QCPPainter *painter, const QCPFinancialDataMap::const_iterator &begin,
21894 const QCPFinancialDataMap::const_iterator &end) {
21895 QCPAxis *keyAxis = mKeyAxis.data();
21896 QCPAxis *valueAxis = mValueAxis.data();
21897 if (!keyAxis || !valueAxis) {
21898 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
21899 return;
21900 }
21901
21902 QPen linePen;
21903
21904 if (keyAxis->orientation() == Qt::Horizontal) {
21905 for (QCPFinancialDataMap::const_iterator it = begin; it != end; ++it) {
21906 if (mSelected)
21907 linePen = mSelectedPen;
21908 else if (mTwoColored)
21909 linePen =
21910 it.value().close >= it.value().open ? mPenPositive : mPenNegative;
21911 else
21912 linePen = mPen;
21913 painter->setPen(linePen);
21914 double keyPixel = keyAxis->coordToPixel(it.value().key);
21915 double openPixel = valueAxis->coordToPixel(it.value().open);
21916 double closePixel = valueAxis->coordToPixel(it.value().close);
21917 // draw backbone:
21918 painter->drawLine(
21919 QPointF(keyPixel, valueAxis->coordToPixel(it.value().high)),
21920 QPointF(keyPixel, valueAxis->coordToPixel(it.value().low)));
21921 // draw open:
21922 double keyWidthPixels =
21923 keyPixel - keyAxis->coordToPixel(
21924 it.value().key -
21925 mWidth * 0.5); // sign of this makes sure open/close
21926 // are on correct sides
21927 painter->drawLine(QPointF(keyPixel - keyWidthPixels, openPixel),
21928 QPointF(keyPixel, openPixel));
21929 // draw close:
21930 painter->drawLine(QPointF(keyPixel, closePixel),
21931 QPointF(keyPixel + keyWidthPixels, closePixel));
21932 }
21933 } else {
21934 for (QCPFinancialDataMap::const_iterator it = begin; it != end; ++it) {
21935 if (mSelected)
21936 linePen = mSelectedPen;
21937 else if (mTwoColored)
21938 linePen =
21939 it.value().close >= it.value().open ? mPenPositive : mPenNegative;
21940 else
21941 linePen = mPen;
21942 painter->setPen(linePen);
21943 double keyPixel = keyAxis->coordToPixel(it.value().key);
21944 double openPixel = valueAxis->coordToPixel(it.value().open);
21945 double closePixel = valueAxis->coordToPixel(it.value().close);
21946 // draw backbone:
21947 painter->drawLine(
21948 QPointF(valueAxis->coordToPixel(it.value().high), keyPixel),
21949 QPointF(valueAxis->coordToPixel(it.value().low), keyPixel));
21950 // draw open:
21951 double keyWidthPixels =
21952 keyPixel - keyAxis->coordToPixel(
21953 it.value().key -
21954 mWidth * 0.5); // sign of this makes sure open/close
21955 // are on correct sides
21956 painter->drawLine(QPointF(openPixel, keyPixel - keyWidthPixels),
21957 QPointF(openPixel, keyPixel));
21958 // draw close:
21959 painter->drawLine(QPointF(closePixel, keyPixel),
21960 QPointF(closePixel, keyPixel + keyWidthPixels));
21961 }
21962 }
21963 }
21964
21965 /*! \internal
21966
21967 Draws the data from \a begin to \a end as Candlesticks with the provided \a
21968 painter.
21969
21970 This method is a helper function for \ref draw. It is used when the chart
21971 style is \ref csCandlestick.
21972 */
21973 void QCPFinancial::drawCandlestickPlot(
21974 QCPPainter *painter, const QCPFinancialDataMap::const_iterator &begin,
21975 const QCPFinancialDataMap::const_iterator &end) {
21976 QCPAxis *keyAxis = mKeyAxis.data();
21977 QCPAxis *valueAxis = mValueAxis.data();
21978 if (!keyAxis || !valueAxis) {
21979 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
21980 return;
21981 }
21982
21983 QPen linePen;
21984 QBrush boxBrush;
21985
21986 if (keyAxis->orientation() == Qt::Horizontal) {
21987 for (QCPFinancialDataMap::const_iterator it = begin; it != end; ++it) {
21988 if (mSelected) {
21989 linePen = mSelectedPen;
21990 boxBrush = mSelectedBrush;
21991 } else if (mTwoColored) {
21992 if (it.value().close >= it.value().open) {
21993 linePen = mPenPositive;
21994 boxBrush = mBrushPositive;
21995 } else {
21996 linePen = mPenNegative;
21997 boxBrush = mBrushNegative;
21998 }
21999 } else {
22000 linePen = mPen;
22001 boxBrush = mBrush;
22002 }
22003 painter->setPen(linePen);
22004 painter->setBrush(boxBrush);
22005 double keyPixel = keyAxis->coordToPixel(it.value().key);
22006 double openPixel = valueAxis->coordToPixel(it.value().open);
22007 double closePixel = valueAxis->coordToPixel(it.value().close);
22008 // draw high:
22009 painter->drawLine(
22010 QPointF(keyPixel, valueAxis->coordToPixel(it.value().high)),
22011 QPointF(keyPixel, valueAxis->coordToPixel(
22012 qMax(it.value().open, it.value().close))));
22013 // draw low:
22014 painter->drawLine(
22015 QPointF(keyPixel, valueAxis->coordToPixel(it.value().low)),
22016 QPointF(keyPixel, valueAxis->coordToPixel(
22017 qMin(it.value().open, it.value().close))));
22018 // draw open-close box:
22019 double keyWidthPixels =
22020 keyPixel - keyAxis->coordToPixel(it.value().key - mWidth * 0.5);
22021 painter->drawRect(QRectF(QPointF(keyPixel - keyWidthPixels, closePixel),
22022 QPointF(keyPixel + keyWidthPixels, openPixel)));
22023 }
22024 } else // keyAxis->orientation() == Qt::Vertical
22025 {
22026 for (QCPFinancialDataMap::const_iterator it = begin; it != end; ++it) {
22027 if (mSelected) {
22028 linePen = mSelectedPen;
22029 boxBrush = mSelectedBrush;
22030 } else if (mTwoColored) {
22031 if (it.value().close >= it.value().open) {
22032 linePen = mPenPositive;
22033 boxBrush = mBrushPositive;
22034 } else {
22035 linePen = mPenNegative;
22036 boxBrush = mBrushNegative;
22037 }
22038 } else {
22039 linePen = mPen;
22040 boxBrush = mBrush;
22041 }
22042 painter->setPen(linePen);
22043 painter->setBrush(boxBrush);
22044 double keyPixel = keyAxis->coordToPixel(it.value().key);
22045 double openPixel = valueAxis->coordToPixel(it.value().open);
22046 double closePixel = valueAxis->coordToPixel(it.value().close);
22047 // draw high:
22048 painter->drawLine(
22049 QPointF(valueAxis->coordToPixel(it.value().high), keyPixel),
22050 QPointF(
22051 valueAxis->coordToPixel(qMax(it.value().open, it.value().close)),
22052 keyPixel));
22053 // draw low:
22054 painter->drawLine(
22055 QPointF(valueAxis->coordToPixel(it.value().low), keyPixel),
22056 QPointF(
22057 valueAxis->coordToPixel(qMin(it.value().open, it.value().close)),
22058 keyPixel));
22059 // draw open-close box:
22060 double keyWidthPixels =
22061 keyPixel - keyAxis->coordToPixel(it.value().key - mWidth * 0.5);
22062 painter->drawRect(QRectF(QPointF(closePixel, keyPixel - keyWidthPixels),
22063 QPointF(openPixel, keyPixel + keyWidthPixels)));
22064 }
22065 }
22066 }
22067
22068 /*! \internal
22069
22070 This method is a helper function for \ref selectTest. It is used to test for
22071 selection when the chart style is \ref csOhlc. It only tests against the data
22072 points between \a begin and \a end.
22073 */
22074 double QCPFinancial::ohlcSelectTest(
22075 const QPointF &pos, const QCPFinancialDataMap::const_iterator &begin,
22076 const QCPFinancialDataMap::const_iterator &end) const {
22077 QCPAxis *keyAxis = mKeyAxis.data();
22078 QCPAxis *valueAxis = mValueAxis.data();
22079 if (!keyAxis || !valueAxis) {
22080 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
22081 return -1;
22082 }
22083
22084 double minDistSqr = std::numeric_limits<double>::max();
22085 QCPFinancialDataMap::const_iterator it;
22086 if (keyAxis->orientation() == Qt::Horizontal) {
22087 for (it = begin; it != end; ++it) {
22088 double keyPixel = keyAxis->coordToPixel(it.value().key);
22089 // calculate distance to backbone:
22090 double currentDistSqr = distSqrToLine(
22091 QPointF(keyPixel, valueAxis->coordToPixel(it.value().high)),
22092 QPointF(keyPixel, valueAxis->coordToPixel(it.value().low)), pos);
22093 if (currentDistSqr < minDistSqr) minDistSqr = currentDistSqr;
22094 }
22095 } else // keyAxis->orientation() == Qt::Vertical
22096 {
22097 for (it = begin; it != end; ++it) {
22098 double keyPixel = keyAxis->coordToPixel(it.value().key);
22099 // calculate distance to backbone:
22100 double currentDistSqr = distSqrToLine(
22101 QPointF(valueAxis->coordToPixel(it.value().high), keyPixel),
22102 QPointF(valueAxis->coordToPixel(it.value().low), keyPixel), pos);
22103 if (currentDistSqr < minDistSqr) minDistSqr = currentDistSqr;
22104 }
22105 }
22106 return qSqrt(minDistSqr);
22107 }
22108
22109 /*! \internal
22110
22111 This method is a helper function for \ref selectTest. It is used to test for
22112 selection when the chart style is \ref csCandlestick. It only tests against
22113 the data points between \a begin and \a end.
22114 */
22115 double QCPFinancial::candlestickSelectTest(
22116 const QPointF &pos, const QCPFinancialDataMap::const_iterator &begin,
22117 const QCPFinancialDataMap::const_iterator &end) const {
22118 QCPAxis *keyAxis = mKeyAxis.data();
22119 QCPAxis *valueAxis = mValueAxis.data();
22120 if (!keyAxis || !valueAxis) {
22121 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
22122 return -1;
22123 }
22124
22125 double minDistSqr = std::numeric_limits<double>::max();
22126 QCPFinancialDataMap::const_iterator it;
22127 if (keyAxis->orientation() == Qt::Horizontal) {
22128 for (it = begin; it != end; ++it) {
22129 double currentDistSqr;
22130 // determine whether pos is in open-close-box:
22131 QCPRange boxKeyRange(it.value().key - mWidth * 0.5,
22132 it.value().key + mWidth * 0.5);
22133 QCPRange boxValueRange(it.value().close, it.value().open);
22134 double posKey, posValue;
22135 pixelsToCoords(pos, posKey, posValue);
22136 if (boxKeyRange.contains(posKey) &&
22137 boxValueRange.contains(posValue)) // is in open-close-box
22138 {
22139 currentDistSqr = mParentPlot->selectionTolerance() * 0.99 *
22140 mParentPlot->selectionTolerance() * 0.99;
22141 } else {
22142 // calculate distance to high/low lines:
22143 double keyPixel = keyAxis->coordToPixel(it.value().key);
22144 double highLineDistSqr = distSqrToLine(
22145 QPointF(keyPixel, valueAxis->coordToPixel(it.value().high)),
22146 QPointF(keyPixel, valueAxis->coordToPixel(
22147 qMax(it.value().open, it.value().close))),
22148 pos);
22149 double lowLineDistSqr = distSqrToLine(
22150 QPointF(keyPixel, valueAxis->coordToPixel(it.value().low)),
22151 QPointF(keyPixel, valueAxis->coordToPixel(
22152 qMin(it.value().open, it.value().close))),
22153 pos);
22154 currentDistSqr = qMin(highLineDistSqr, lowLineDistSqr);
22155 }
22156 if (currentDistSqr < minDistSqr) minDistSqr = currentDistSqr;
22157 }
22158 } else // keyAxis->orientation() == Qt::Vertical
22159 {
22160 for (it = begin; it != end; ++it) {
22161 double currentDistSqr;
22162 // determine whether pos is in open-close-box:
22163 QCPRange boxKeyRange(it.value().key - mWidth * 0.5,
22164 it.value().key + mWidth * 0.5);
22165 QCPRange boxValueRange(it.value().close, it.value().open);
22166 double posKey, posValue;
22167 pixelsToCoords(pos, posKey, posValue);
22168 if (boxKeyRange.contains(posKey) &&
22169 boxValueRange.contains(posValue)) // is in open-close-box
22170 {
22171 currentDistSqr = mParentPlot->selectionTolerance() * 0.99 *
22172 mParentPlot->selectionTolerance() * 0.99;
22173 } else {
22174 // calculate distance to high/low lines:
22175 double keyPixel = keyAxis->coordToPixel(it.value().key);
22176 double highLineDistSqr = distSqrToLine(
22177 QPointF(valueAxis->coordToPixel(it.value().high), keyPixel),
22178 QPointF(valueAxis->coordToPixel(
22179 qMax(it.value().open, it.value().close)),
22180 keyPixel),
22181 pos);
22182 double lowLineDistSqr = distSqrToLine(
22183 QPointF(valueAxis->coordToPixel(it.value().low), keyPixel),
22184 QPointF(valueAxis->coordToPixel(
22185 qMin(it.value().open, it.value().close)),
22186 keyPixel),
22187 pos);
22188 currentDistSqr = qMin(highLineDistSqr, lowLineDistSqr);
22189 }
22190 if (currentDistSqr < minDistSqr) minDistSqr = currentDistSqr;
22191 }
22192 }
22193 return qSqrt(minDistSqr);
22194 }
22195
22196 /*! \internal
22197
22198 called by the drawing methods to determine which data (key) range is visible
22199 at the current key axis range setting, so only that needs to be processed.
22200
22201 \a lower returns an iterator to the lowest data point that needs to be taken
22202 into account when plotting. Note that in order to get a clean plot all the way
22203 to the edge of the axis rect, \a lower may still be just outside the visible
22204 range.
22205
22206 \a upper returns an iterator to the highest data point. Same as before, \a
22207 upper may also lie just outside of the visible range.
22208
22209 if the plottable contains no data, both \a lower and \a upper point to
22210 constEnd.
22211
22212 \see QCPGraph::getVisibleDataBounds
22213 */
22214 void QCPFinancial::getVisibleDataBounds(
22215 QCPFinancialDataMap::const_iterator &lower,
22216 QCPFinancialDataMap::const_iterator &upper) const {
22217 if (!mKeyAxis) {
22218 qDebug() << Q_FUNC_INFO << "invalid key axis";
22219 return;
22220 }
22221 if (mData->isEmpty()) {
22222 lower = mData->constEnd();
22223 upper = mData->constEnd();
22224 return;
22225 }
22226
22227 // get visible data range as QMap iterators
22228 QCPFinancialDataMap::const_iterator lbound =
22229 mData->lowerBound(mKeyAxis.data()->range().lower);
22230 QCPFinancialDataMap::const_iterator ubound =
22231 mData->upperBound(mKeyAxis.data()->range().upper);
22232 bool lowoutlier =
22233 lbound != mData->constBegin(); // indicates whether there exist points
22234 // below axis range
22235 bool highoutlier =
22236 ubound != mData->constEnd(); // indicates whether there exist points
22237 // above axis range
22238
22239 lower =
22240 (lowoutlier ? lbound - 1
22241 : lbound); // data point range that will be actually drawn
22242 upper = (highoutlier
22243 ? ubound
22244 : ubound - 1); // data point range that will be actually drawn
22245 }
22246
22247 ////////////////////////////////////////////////////////////////////////////////////////////////////
22248 //////////////////// QCPItemStraightLine
22249 ////////////////////////////////////////////////////////////////////////////////////////////////////
22250
22251 /*! \class QCPItemStraightLine
22252 \brief A straight line that spans infinitely in both directions
22253
22254 \image html QCPItemStraightLine.png "Straight line example. Blue dotted
22255 circles are anchors, solid blue discs are positions."
22256
22257 It has two positions, \a point1 and \a point2, which define the straight line.
22258 */
22259
22260 /*!
22261 Creates a straight line item and sets default values.
22262
22263 The constructed item can be added to the plot with QCustomPlot::addItem.
22264 */
22265 QCPItemStraightLine::QCPItemStraightLine(QCustomPlot *parentPlot)
22266 : QCPAbstractItem(parentPlot),
22267 point1(createPosition(QLatin1String("point1"))),
22268 point2(createPosition(QLatin1String("point2"))) {
22269 point1->setCoords(0, 0);
22270 point2->setCoords(1, 1);
22271
22272 setPen(QPen(Qt::black));
22273 setSelectedPen(QPen(Qt::blue, 2));
22274 }
22275
22276 QCPItemStraightLine::~QCPItemStraightLine() {}
22277
22278 /*!
22279 Sets the pen that will be used to draw the line
22280
22281 \see setSelectedPen
22282 */
22283 void QCPItemStraightLine::setPen(const QPen &pen) { mPen = pen; }
22284
22285 /*!
22286 Sets the pen that will be used to draw the line when selected
22287
22288 \see setPen, setSelected
22289 */
22290 void QCPItemStraightLine::setSelectedPen(const QPen &pen) {
22291 mSelectedPen = pen;
22292 }
22293
22294 /* inherits documentation from base class */
22295 double QCPItemStraightLine::selectTest(const QPointF &pos, bool onlySelectable,
22296 QVariant *details) const {
22297 Q_UNUSED(details)
22298 if (onlySelectable && !mSelectable) return -1;
22299
22300 return distToStraightLine(
22301 QVector2D(point1->pixelPoint()),
22302 QVector2D(point2->pixelPoint() - point1->pixelPoint()), QVector2D(pos));
22303 }
22304
22305 /* inherits documentation from base class */
22306 void QCPItemStraightLine::draw(QCPPainter *painter) {
22307 QVector2D start(point1->pixelPoint());
22308 QVector2D end(point2->pixelPoint());
22309 // get visible segment of straight line inside clipRect:
22310 double clipPad = mainPen().widthF();
22311 QLineF line = getRectClippedStraightLine(
22312 start, end - start,
22313 clipRect().adjusted(-clipPad, -clipPad, clipPad, clipPad));
22314 // paint visible segment, if existent:
22315 if (!line.isNull()) {
22316 painter->setPen(mainPen());
22317 painter->drawLine(line);
22318 }
22319 }
22320
22321 /*! \internal
22322
22323 finds the shortest distance of \a point to the straight line defined by the
22324 base point \a base and the direction vector \a vec.
22325
22326 This is a helper function for \ref selectTest.
22327 */
22328 double QCPItemStraightLine::distToStraightLine(const QVector2D &base,
22329 const QVector2D &vec,
22330 const QVector2D &point) const {
22331 return qAbs((base.y() - point.y()) * vec.x() -
22332 (base.x() - point.x()) * vec.y()) /
22333 vec.length();
22334 }
22335
22336 /*! \internal
22337
22338 Returns the section of the straight line defined by \a base and direction
22339 vector \a vec, that is visible in the specified \a rect.
22340
22341 This is a helper function for \ref draw.
22342 */
22343 QLineF QCPItemStraightLine::getRectClippedStraightLine(
22344 const QVector2D &base, const QVector2D &vec, const QRect &rect) const {
22345 double bx, by;
22346 double gamma;
22347 QLineF result;
22348 if (vec.x() == 0 && vec.y() == 0) return result;
22349 if (qFuzzyIsNull(vec.x())) // line is vertical
22350 {
22351 // check top of rect:
22352 bx = rect.left();
22353 by = rect.top();
22354 gamma = base.x() - bx + (by - base.y()) * vec.x() / vec.y();
22355 if (gamma >= 0 && gamma <= rect.width())
22356 result.setLine(bx + gamma, rect.top(), bx + gamma,
22357 rect.bottom()); // no need to check bottom because we know
22358 // line is vertical
22359 } else if (qFuzzyIsNull(vec.y())) // line is horizontal
22360 {
22361 // check left of rect:
22362 bx = rect.left();
22363 by = rect.top();
22364 gamma = base.y() - by + (bx - base.x()) * vec.y() / vec.x();
22365 if (gamma >= 0 && gamma <= rect.height())
22366 result.setLine(rect.left(), by + gamma, rect.right(),
22367 by + gamma); // no need to check right because we know
22368 // line is horizontal
22369 } else // line is skewed
22370 {
22371 QList<QVector2D> pointVectors;
22372 // check top of rect:
22373 bx = rect.left();
22374 by = rect.top();
22375 gamma = base.x() - bx + (by - base.y()) * vec.x() / vec.y();
22376 if (gamma >= 0 && gamma <= rect.width())
22377 pointVectors.append(QVector2D(bx + gamma, by));
22378 // check bottom of rect:
22379 bx = rect.left();
22380 by = rect.bottom();
22381 gamma = base.x() - bx + (by - base.y()) * vec.x() / vec.y();
22382 if (gamma >= 0 && gamma <= rect.width())
22383 pointVectors.append(QVector2D(bx + gamma, by));
22384 // check left of rect:
22385 bx = rect.left();
22386 by = rect.top();
22387 gamma = base.y() - by + (bx - base.x()) * vec.y() / vec.x();
22388 if (gamma >= 0 && gamma <= rect.height())
22389 pointVectors.append(QVector2D(bx, by + gamma));
22390 // check right of rect:
22391 bx = rect.right();
22392 by = rect.top();
22393 gamma = base.y() - by + (bx - base.x()) * vec.y() / vec.x();
22394 if (gamma >= 0 && gamma <= rect.height())
22395 pointVectors.append(QVector2D(bx, by + gamma));
22396
22397 // evaluate points:
22398 if (pointVectors.size() == 2) {
22399 result.setPoints(pointVectors.at(0).toPointF(),
22400 pointVectors.at(1).toPointF());
22401 } else if (pointVectors.size() > 2) {
22402 // line probably goes through corner of rect, and we got two points there.
22403 // single out the point pair with greatest distance:
22404 double distSqrMax = 0;
22405 QVector2D pv1, pv2;
22406 for (int i = 0; i < pointVectors.size() - 1; ++i) {
22407 for (int k = i + 1; k < pointVectors.size(); ++k) {
22408 double distSqr =
22409 (pointVectors.at(i) - pointVectors.at(k)).lengthSquared();
22410 if (distSqr > distSqrMax) {
22411 pv1 = pointVectors.at(i);
22412 pv2 = pointVectors.at(k);
22413 distSqrMax = distSqr;
22414 }
22415 }
22416 }
22417 result.setPoints(pv1.toPointF(), pv2.toPointF());
22418 }
22419 }
22420 return result;
22421 }
22422
22423 /*! \internal
22424
22425 Returns the pen that should be used for drawing lines. Returns mPen when the
22426 item is not selected and mSelectedPen when it is.
22427 */
22428 QPen QCPItemStraightLine::mainPen() const {
22429 return mSelected ? mSelectedPen : mPen;
22430 }
22431
22432 ////////////////////////////////////////////////////////////////////////////////////////////////////
22433 //////////////////// QCPItemLine
22434 ////////////////////////////////////////////////////////////////////////////////////////////////////
22435
22436 /*! \class QCPItemLine
22437 \brief A line from one point to another
22438
22439 \image html QCPItemLine.png "Line example. Blue dotted circles are anchors,
22440 solid blue discs are positions."
22441
22442 It has two positions, \a start and \a end, which define the end points of the
22443 line.
22444
22445 With \ref setHead and \ref setTail you may set different line ending styles,
22446 e.g. to create an arrow.
22447 */
22448
22449 /*!
22450 Creates a line item and sets default values.
22451
22452 The constructed item can be added to the plot with QCustomPlot::addItem.
22453 */
22454 QCPItemLine::QCPItemLine(QCustomPlot *parentPlot)
22455 : QCPAbstractItem(parentPlot),
22456 start(createPosition(QLatin1String("start"))),
22457 end(createPosition(QLatin1String("end"))) {
22458 start->setCoords(0, 0);
22459 end->setCoords(1, 1);
22460
22461 setPen(QPen(Qt::black));
22462 setSelectedPen(QPen(Qt::blue, 2));
22463 }
22464
22465 QCPItemLine::~QCPItemLine() {}
22466
22467 /*!
22468 Sets the pen that will be used to draw the line
22469
22470 \see setSelectedPen
22471 */
22472 void QCPItemLine::setPen(const QPen &pen) { mPen = pen; }
22473
22474 /*!
22475 Sets the pen that will be used to draw the line when selected
22476
22477 \see setPen, setSelected
22478 */
22479 void QCPItemLine::setSelectedPen(const QPen &pen) { mSelectedPen = pen; }
22480
22481 /*!
22482 Sets the line ending style of the head. The head corresponds to the \a end
22483 position.
22484
22485 Note that due to the overloaded QCPLineEnding constructor, you may directly
22486 specify a QCPLineEnding::EndingStyle here, e.g. \code
22487 setHead(QCPLineEnding::esSpikeArrow) \endcode
22488
22489 \see setTail
22490 */
22491 void QCPItemLine::setHead(const QCPLineEnding &head) { mHead = head; }
22492
22493 /*!
22494 Sets the line ending style of the tail. The tail corresponds to the \a start
22495 position.
22496
22497 Note that due to the overloaded QCPLineEnding constructor, you may directly
22498 specify a QCPLineEnding::EndingStyle here, e.g. \code
22499 setTail(QCPLineEnding::esSpikeArrow) \endcode
22500
22501 \see setHead
22502 */
22503 void QCPItemLine::setTail(const QCPLineEnding &tail) { mTail = tail; }
22504
22505 /* inherits documentation from base class */
22506 double QCPItemLine::selectTest(const QPointF &pos, bool onlySelectable,
22507 QVariant *details) const {
22508 Q_UNUSED(details)
22509 if (onlySelectable && !mSelectable) return -1;
22510
22511 return qSqrt(distSqrToLine(start->pixelPoint(), end->pixelPoint(), pos));
22512 }
22513
22514 /* inherits documentation from base class */
22515 void QCPItemLine::draw(QCPPainter *painter) {
22516 QVector2D startVec(start->pixelPoint());
22517 QVector2D endVec(end->pixelPoint());
22518 if (startVec.toPoint() == endVec.toPoint()) return;
22519 // get visible segment of straight line inside clipRect:
22520 double clipPad = qMax(mHead.boundingDistance(), mTail.boundingDistance());
22521 clipPad = qMax(clipPad, (double)mainPen().widthF());
22522 QLineF line = getRectClippedLine(
22523 startVec, endVec,
22524 clipRect().adjusted(-clipPad, -clipPad, clipPad, clipPad));
22525 // paint visible segment, if existent:
22526 if (!line.isNull()) {
22527 painter->setPen(mainPen());
22528 painter->drawLine(line);
22529 painter->setBrush(Qt::SolidPattern);
22530 if (mTail.style() != QCPLineEnding::esNone)
22531 mTail.draw(painter, startVec, startVec - endVec);
22532 if (mHead.style() != QCPLineEnding::esNone)
22533 mHead.draw(painter, endVec, endVec - startVec);
22534 }
22535 }
22536
22537 /*! \internal
22538
22539 Returns the section of the line defined by \a start and \a end, that is
22540 visible in the specified \a rect.
22541
22542 This is a helper function for \ref draw.
22543 */
22544 QLineF QCPItemLine::getRectClippedLine(const QVector2D &start,
22545 const QVector2D &end,
22546 const QRect &rect) const {
22547 bool containsStart = rect.contains(start.x(), start.y());
22548 bool containsEnd = rect.contains(end.x(), end.y());
22549 if (containsStart && containsEnd)
22550 return QLineF(start.toPointF(), end.toPointF());
22551
22552 QVector2D base = start;
22553 QVector2D vec = end - start;
22554 double bx, by;
22555 double gamma, mu;
22556 QLineF result;
22557 QList<QVector2D> pointVectors;
22558
22559 if (!qFuzzyIsNull(vec.y())) // line is not horizontal
22560 {
22561 // check top of rect:
22562 bx = rect.left();
22563 by = rect.top();
22564 mu = (by - base.y()) / vec.y();
22565 if (mu >= 0 && mu <= 1) {
22566 gamma = base.x() - bx + mu * vec.x();
22567 if (gamma >= 0 && gamma <= rect.width())
22568 pointVectors.append(QVector2D(bx + gamma, by));
22569 }
22570 // check bottom of rect:
22571 bx = rect.left();
22572 by = rect.bottom();
22573 mu = (by - base.y()) / vec.y();
22574 if (mu >= 0 && mu <= 1) {
22575 gamma = base.x() - bx + mu * vec.x();
22576 if (gamma >= 0 && gamma <= rect.width())
22577 pointVectors.append(QVector2D(bx + gamma, by));
22578 }
22579 }
22580 if (!qFuzzyIsNull(vec.x())) // line is not vertical
22581 {
22582 // check left of rect:
22583 bx = rect.left();
22584 by = rect.top();
22585 mu = (bx - base.x()) / vec.x();
22586 if (mu >= 0 && mu <= 1) {
22587 gamma = base.y() - by + mu * vec.y();
22588 if (gamma >= 0 && gamma <= rect.height())
22589 pointVectors.append(QVector2D(bx, by + gamma));
22590 }
22591 // check right of rect:
22592 bx = rect.right();
22593 by = rect.top();
22594 mu = (bx - base.x()) / vec.x();
22595 if (mu >= 0 && mu <= 1) {
22596 gamma = base.y() - by + mu * vec.y();
22597 if (gamma >= 0 && gamma <= rect.height())
22598 pointVectors.append(QVector2D(bx, by + gamma));
22599 }
22600 }
22601
22602 if (containsStart) pointVectors.append(start);
22603 if (containsEnd) pointVectors.append(end);
22604
22605 // evaluate points:
22606 if (pointVectors.size() == 2) {
22607 result.setPoints(pointVectors.at(0).toPointF(),
22608 pointVectors.at(1).toPointF());
22609 } else if (pointVectors.size() > 2) {
22610 // line probably goes through corner of rect, and we got two points there.
22611 // single out the point pair with greatest distance:
22612 double distSqrMax = 0;
22613 QVector2D pv1, pv2;
22614 for (int i = 0; i < pointVectors.size() - 1; ++i) {
22615 for (int k = i + 1; k < pointVectors.size(); ++k) {
22616 double distSqr =
22617 (pointVectors.at(i) - pointVectors.at(k)).lengthSquared();
22618 if (distSqr > distSqrMax) {
22619 pv1 = pointVectors.at(i);
22620 pv2 = pointVectors.at(k);
22621 distSqrMax = distSqr;
22622 }
22623 }
22624 }
22625 result.setPoints(pv1.toPointF(), pv2.toPointF());
22626 }
22627 return result;
22628 }
22629
22630 /*! \internal
22631
22632 Returns the pen that should be used for drawing lines. Returns mPen when the
22633 item is not selected and mSelectedPen when it is.
22634 */
22635 QPen QCPItemLine::mainPen() const { return mSelected ? mSelectedPen : mPen; }
22636
22637 ////////////////////////////////////////////////////////////////////////////////////////////////////
22638 //////////////////// QCPItemCurve
22639 ////////////////////////////////////////////////////////////////////////////////////////////////////
22640
22641 /*! \class QCPItemCurve
22642 \brief A curved line from one point to another
22643
22644 \image html QCPItemCurve.png "Curve example. Blue dotted circles are anchors,
22645 solid blue discs are positions."
22646
22647 It has four positions, \a start and \a end, which define the end points of the
22648 line, and two control points which define the direction the line exits from
22649 the start and the direction from which it approaches the end: \a startDir and
22650 \a endDir.
22651
22652 With \ref setHead and \ref setTail you may set different line ending styles,
22653 e.g. to create an arrow.
22654
22655 Often it is desirable for the control points to stay at fixed relative
22656 positions to the start/end point. This can be achieved by setting the parent
22657 anchor e.g. of \a startDir simply to \a start, and then specify the desired
22658 pixel offset with QCPItemPosition::setCoords on \a startDir.
22659 */
22660
22661 /*!
22662 Creates a curve item and sets default values.
22663
22664 The constructed item can be added to the plot with QCustomPlot::addItem.
22665 */
22666 QCPItemCurve::QCPItemCurve(QCustomPlot *parentPlot)
22667 : QCPAbstractItem(parentPlot),
22668 start(createPosition(QLatin1String("start"))),
22669 startDir(createPosition(QLatin1String("startDir"))),
22670 endDir(createPosition(QLatin1String("endDir"))),
22671 end(createPosition(QLatin1String("end"))) {
22672 start->setCoords(0, 0);
22673 startDir->setCoords(0.5, 0);
22674 endDir->setCoords(0, 0.5);
22675 end->setCoords(1, 1);
22676
22677 setPen(QPen(Qt::black));
22678 setSelectedPen(QPen(Qt::blue, 2));
22679 }
22680
22681 QCPItemCurve::~QCPItemCurve() {}
22682
22683 /*!
22684 Sets the pen that will be used to draw the line
22685
22686 \see setSelectedPen
22687 */
22688 void QCPItemCurve::setPen(const QPen &pen) { mPen = pen; }
22689
22690 /*!
22691 Sets the pen that will be used to draw the line when selected
22692
22693 \see setPen, setSelected
22694 */
22695 void QCPItemCurve::setSelectedPen(const QPen &pen) { mSelectedPen = pen; }
22696
22697 /*!
22698 Sets the line ending style of the head. The head corresponds to the \a end
22699 position.
22700
22701 Note that due to the overloaded QCPLineEnding constructor, you may directly
22702 specify a QCPLineEnding::EndingStyle here, e.g. \code
22703 setHead(QCPLineEnding::esSpikeArrow) \endcode
22704
22705 \see setTail
22706 */
22707 void QCPItemCurve::setHead(const QCPLineEnding &head) { mHead = head; }
22708
22709 /*!
22710 Sets the line ending style of the tail. The tail corresponds to the \a start
22711 position.
22712
22713 Note that due to the overloaded QCPLineEnding constructor, you may directly
22714 specify a QCPLineEnding::EndingStyle here, e.g. \code
22715 setTail(QCPLineEnding::esSpikeArrow) \endcode
22716
22717 \see setHead
22718 */
22719 void QCPItemCurve::setTail(const QCPLineEnding &tail) { mTail = tail; }
22720
22721 /* inherits documentation from base class */
22722 double QCPItemCurve::selectTest(const QPointF &pos, bool onlySelectable,
22723 QVariant *details) const {
22724 Q_UNUSED(details)
22725 if (onlySelectable && !mSelectable) return -1;
22726
22727 QPointF startVec(start->pixelPoint());
22728 QPointF startDirVec(startDir->pixelPoint());
22729 QPointF endDirVec(endDir->pixelPoint());
22730 QPointF endVec(end->pixelPoint());
22731
22732 QPainterPath cubicPath(startVec);
22733 cubicPath.cubicTo(startDirVec, endDirVec, endVec);
22734
22735 QPolygonF polygon = cubicPath.toSubpathPolygons().first();
22736 double minDistSqr = std::numeric_limits<double>::max();
22737 for (int i = 1; i < polygon.size(); ++i) {
22738 double distSqr = distSqrToLine(polygon.at(i - 1), polygon.at(i), pos);
22739 if (distSqr < minDistSqr) minDistSqr = distSqr;
22740 }
22741 return qSqrt(minDistSqr);
22742 }
22743
22744 /* inherits documentation from base class */
22745 void QCPItemCurve::draw(QCPPainter *painter) {
22746 QPointF startVec(start->pixelPoint());
22747 QPointF startDirVec(startDir->pixelPoint());
22748 QPointF endDirVec(endDir->pixelPoint());
22749 QPointF endVec(end->pixelPoint());
22750 if (QVector2D(endVec - startVec).length() >
22751 1e10f) // too large curves cause crash
22752 return;
22753
22754 QPainterPath cubicPath(startVec);
22755 cubicPath.cubicTo(startDirVec, endDirVec, endVec);
22756
22757 // paint visible segment, if existent:
22758 QRect clip = clipRect().adjusted(-mainPen().widthF(), -mainPen().widthF(),
22759 mainPen().widthF(), mainPen().widthF());
22760 QRect cubicRect = cubicPath.controlPointRect().toRect();
22761 if (cubicRect.isEmpty()) // may happen when start and end exactly on same x
22762 // or y position
22763 cubicRect.adjust(0, 0, 1, 1);
22764 if (clip.intersects(cubicRect)) {
22765 painter->setPen(mainPen());
22766 painter->drawPath(cubicPath);
22767 painter->setBrush(Qt::SolidPattern);
22768 if (mTail.style() != QCPLineEnding::esNone)
22769 mTail.draw(painter, QVector2D(startVec),
22770 M_PI - cubicPath.angleAtPercent(0) / 180.0 * M_PI);
22771 if (mHead.style() != QCPLineEnding::esNone)
22772 mHead.draw(painter, QVector2D(endVec),
22773 -cubicPath.angleAtPercent(1) / 180.0 * M_PI);
22774 }
22775 }
22776
22777 /*! \internal
22778
22779 Returns the pen that should be used for drawing lines. Returns mPen when the
22780 item is not selected and mSelectedPen when it is.
22781 */
22782 QPen QCPItemCurve::mainPen() const { return mSelected ? mSelectedPen : mPen; }
22783
22784 ////////////////////////////////////////////////////////////////////////////////////////////////////
22785 //////////////////// QCPItemRect
22786 ////////////////////////////////////////////////////////////////////////////////////////////////////
22787
22788 /*! \class QCPItemRect
22789 \brief A rectangle
22790
22791 \image html QCPItemRect.png "Rectangle example. Blue dotted circles are
22792 anchors, solid blue discs are positions."
22793
22794 It has two positions, \a topLeft and \a bottomRight, which define the
22795 rectangle.
22796 */
22797
22798 /*!
22799 Creates a rectangle item and sets default values.
22800
22801 The constructed item can be added to the plot with QCustomPlot::addItem.
22802 */
22803 QCPItemRect::QCPItemRect(QCustomPlot *parentPlot)
22804 : QCPAbstractItem(parentPlot),
22805 topLeft(createPosition(QLatin1String("topLeft"))),
22806 bottomRight(createPosition(QLatin1String("bottomRight"))),
22807 top(createAnchor(QLatin1String("top"), aiTop)),
22808 topRight(createAnchor(QLatin1String("topRight"), aiTopRight)),
22809 right(createAnchor(QLatin1String("right"), aiRight)),
22810 bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
22811 bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)),
22812 left(createAnchor(QLatin1String("left"), aiLeft)) {
22813 topLeft->setCoords(0, 1);
22814 bottomRight->setCoords(1, 0);
22815
22816 setPen(QPen(Qt::black));
22817 setSelectedPen(QPen(Qt::blue, 2));
22818 setBrush(Qt::NoBrush);
22819 setSelectedBrush(Qt::NoBrush);
22820 }
22821
22822 QCPItemRect::~QCPItemRect() {}
22823
22824 /*!
22825 Sets the pen that will be used to draw the line of the rectangle
22826
22827 \see setSelectedPen, setBrush
22828 */
22829 void QCPItemRect::setPen(const QPen &pen) { mPen = pen; }
22830
22831 /*!
22832 Sets the pen that will be used to draw the line of the rectangle when selected
22833
22834 \see setPen, setSelected
22835 */
22836 void QCPItemRect::setSelectedPen(const QPen &pen) { mSelectedPen = pen; }
22837
22838 /*!
22839 Sets the brush that will be used to fill the rectangle. To disable filling,
22840 set \a brush to Qt::NoBrush.
22841
22842 \see setSelectedBrush, setPen
22843 */
22844 void QCPItemRect::setBrush(const QBrush &brush) { mBrush = brush; }
22845
22846 /*!
22847 Sets the brush that will be used to fill the rectangle when selected. To
22848 disable filling, set \a brush to Qt::NoBrush.
22849
22850 \see setBrush
22851 */
22852 void QCPItemRect::setSelectedBrush(const QBrush &brush) {
22853 mSelectedBrush = brush;
22854 }
22855
22856 /* inherits documentation from base class */
22857 double QCPItemRect::selectTest(const QPointF &pos, bool onlySelectable,
22858 QVariant *details) const {
22859 Q_UNUSED(details)
22860 if (onlySelectable && !mSelectable) return -1;
22861
22862 QRectF rect =
22863 QRectF(topLeft->pixelPoint(), bottomRight->pixelPoint()).normalized();
22864 bool filledRect =
22865 mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0;
22866 return rectSelectTest(rect, pos, filledRect);
22867 }
22868
22869 /* inherits documentation from base class */
22870 void QCPItemRect::draw(QCPPainter *painter) {
22871 QPointF p1 = topLeft->pixelPoint();
22872 QPointF p2 = bottomRight->pixelPoint();
22873 if (p1.toPoint() == p2.toPoint()) return;
22874 QRectF rect = QRectF(p1, p2).normalized();
22875 double clipPad = mainPen().widthF();
22876 QRectF boundingRect = rect.adjusted(-clipPad, -clipPad, clipPad, clipPad);
22877 if (boundingRect.intersects(clipRect())) // only draw if bounding rect of
22878 // rect item is visible in cliprect
22879 {
22880 painter->setPen(mainPen());
22881 painter->setBrush(mainBrush());
22882 painter->drawRect(rect);
22883 }
22884 }
22885
22886 /* inherits documentation from base class */
22887 QPointF QCPItemRect::anchorPixelPoint(int anchorId) const {
22888 QRectF rect = QRectF(topLeft->pixelPoint(), bottomRight->pixelPoint());
22889 switch (anchorId) {
22890 case aiTop:
22891 return (rect.topLeft() + rect.topRight()) * 0.5;
22892 case aiTopRight:
22893 return rect.topRight();
22894 case aiRight:
22895 return (rect.topRight() + rect.bottomRight()) * 0.5;
22896 case aiBottom:
22897 return (rect.bottomLeft() + rect.bottomRight()) * 0.5;
22898 case aiBottomLeft:
22899 return rect.bottomLeft();
22900 case aiLeft:
22901 return (rect.topLeft() + rect.bottomLeft()) * 0.5;
22902 }
22903
22904 qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
22905 return QPointF();
22906 }
22907
22908 /*! \internal
22909
22910 Returns the pen that should be used for drawing lines. Returns mPen when the
22911 item is not selected and mSelectedPen when it is.
22912 */
22913 QPen QCPItemRect::mainPen() const { return mSelected ? mSelectedPen : mPen; }
22914
22915 /*! \internal
22916
22917 Returns the brush that should be used for drawing fills of the item. Returns
22918 mBrush when the item is not selected and mSelectedBrush when it is.
22919 */
22920 QBrush QCPItemRect::mainBrush() const {
22921 return mSelected ? mSelectedBrush : mBrush;
22922 }
22923
22924 ////////////////////////////////////////////////////////////////////////////////////////////////////
22925 //////////////////// QCPItemText
22926 ////////////////////////////////////////////////////////////////////////////////////////////////////
22927
22928 /*! \class QCPItemText
22929 \brief A text label
22930
22931 \image html QCPItemText.png "Text example. Blue dotted circles are anchors,
22932 solid blue discs are positions."
22933
22934 Its position is defined by the member \a position and the setting of \ref
22935 setPositionAlignment. The latter controls which part of the text rect shall be
22936 aligned with \a position.
22937
22938 The text alignment itself (i.e. left, center, right) can be controlled with
22939 \ref setTextAlignment.
22940
22941 The text may be rotated around the \a position point with \ref setRotation.
22942 */
22943
22944 /*!
22945 Creates a text item and sets default values.
22946
22947 The constructed item can be added to the plot with QCustomPlot::addItem.
22948 */
22949 QCPItemText::QCPItemText(QCustomPlot *parentPlot)
22950 : QCPAbstractItem(parentPlot),
22951 position(createPosition(QLatin1String("position"))),
22952 topLeft(createAnchor(QLatin1String("topLeft"), aiTopLeft)),
22953 top(createAnchor(QLatin1String("top"), aiTop)),
22954 topRight(createAnchor(QLatin1String("topRight"), aiTopRight)),
22955 right(createAnchor(QLatin1String("right"), aiRight)),
22956 bottomRight(createAnchor(QLatin1String("bottomRight"), aiBottomRight)),
22957 bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
22958 bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)),
22959 left(createAnchor(QLatin1String("left"), aiLeft)) {
22960 position->setCoords(0, 0);
22961
22962 setRotation(0);
22963 setTextAlignment(Qt::AlignTop | Qt::AlignHCenter);
22964 setPositionAlignment(Qt::AlignCenter);
22965 setText(QLatin1String("text"));
22966
22967 setPen(Qt::NoPen);
22968 setSelectedPen(Qt::NoPen);
22969 setBrush(Qt::NoBrush);
22970 setSelectedBrush(Qt::NoBrush);
22971 setColor(Qt::black);
22972 setSelectedColor(Qt::blue);
22973 }
22974
22975 QCPItemText::~QCPItemText() {}
22976
22977 /*!
22978 Sets the color of the text.
22979 */
22980 void QCPItemText::setColor(const QColor &color) { mColor = color; }
22981
22982 /*!
22983 Sets the color of the text that will be used when the item is selected.
22984 */
22985 void QCPItemText::setSelectedColor(const QColor &color) {
22986 mSelectedColor = color;
22987 }
22988
22989 /*!
22990 Sets the pen that will be used do draw a rectangular border around the text.
22991 To disable the border, set \a pen to Qt::NoPen.
22992
22993 \see setSelectedPen, setBrush, setPadding
22994 */
22995 void QCPItemText::setPen(const QPen &pen) { mPen = pen; }
22996
22997 /*!
22998 Sets the pen that will be used do draw a rectangular border around the text,
22999 when the item is selected. To disable the border, set \a pen to Qt::NoPen.
23000
23001 \see setPen
23002 */
23003 void QCPItemText::setSelectedPen(const QPen &pen) { mSelectedPen = pen; }
23004
23005 /*!
23006 Sets the brush that will be used do fill the background of the text. To
23007 disable the background, set \a brush to Qt::NoBrush.
23008
23009 \see setSelectedBrush, setPen, setPadding
23010 */
23011 void QCPItemText::setBrush(const QBrush &brush) { mBrush = brush; }
23012
23013 /*!
23014 Sets the brush that will be used do fill the background of the text, when the
23015 item is selected. To disable the background, set \a brush to Qt::NoBrush.
23016
23017 \see setBrush
23018 */
23019 void QCPItemText::setSelectedBrush(const QBrush &brush) {
23020 mSelectedBrush = brush;
23021 }
23022
23023 /*!
23024 Sets the font of the text.
23025
23026 \see setSelectedFont, setColor
23027 */
23028 void QCPItemText::setFont(const QFont &font) { mFont = font; }
23029
23030 /*!
23031 Sets the font of the text that will be used when the item is selected.
23032
23033 \see setFont
23034 */
23035 void QCPItemText::setSelectedFont(const QFont &font) { mSelectedFont = font; }
23036
23037 /*!
23038 Sets the text that will be displayed. Multi-line texts are supported by
23039 inserting a line break character, e.g. '\n'.
23040
23041 \see setFont, setColor, setTextAlignment
23042 */
23043 void QCPItemText::setText(const QString &text) { mText = text; }
23044
23045 /*!
23046 Sets which point of the text rect shall be aligned with \a position.
23047
23048 Examples:
23049 \li If \a alignment is <tt>Qt::AlignHCenter | Qt::AlignTop</tt>, the text will
23050 be positioned such that the top of the text rect will be horizontally centered
23051 on \a position. \li If \a alignment is <tt>Qt::AlignLeft |
23052 Qt::AlignBottom</tt>, \a position will indicate the bottom left corner of the
23053 text rect.
23054
23055 If you want to control the alignment of (multi-lined) text within the text
23056 rect, use \ref setTextAlignment.
23057 */
23058 void QCPItemText::setPositionAlignment(Qt::Alignment alignment) {
23059 mPositionAlignment = alignment;
23060 }
23061
23062 /*!
23063 Controls how (multi-lined) text is aligned inside the text rect (typically
23064 Qt::AlignLeft, Qt::AlignCenter or Qt::AlignRight).
23065 */
23066 void QCPItemText::setTextAlignment(Qt::Alignment alignment) {
23067 mTextAlignment = alignment;
23068 }
23069
23070 /*!
23071 Sets the angle in degrees by which the text (and the text rectangle, if
23072 visible) will be rotated around \a position.
23073 */
23074 void QCPItemText::setRotation(double degrees) { mRotation = degrees; }
23075
23076 /*!
23077 Sets the distance between the border of the text rectangle and the text. The
23078 appearance (and visibility) of the text rectangle can be controlled with \ref
23079 setPen and \ref setBrush.
23080 */
23081 void QCPItemText::setPadding(const QMargins &padding) { mPadding = padding; }
23082
23083 /* inherits documentation from base class */
23084 double QCPItemText::selectTest(const QPointF &pos, bool onlySelectable,
23085 QVariant *details) const {
23086 Q_UNUSED(details)
23087 if (onlySelectable && !mSelectable) return -1;
23088
23089 // The rect may be rotated, so we transform the actual clicked pos to the
23090 // rotated coordinate system, so we can use the normal rectSelectTest function
23091 // for non-rotated rects:
23092 QPointF positionPixels(position->pixelPoint());
23093 QTransform inputTransform;
23094 inputTransform.translate(positionPixels.x(), positionPixels.y());
23095 inputTransform.rotate(-mRotation);
23096 inputTransform.translate(-positionPixels.x(), -positionPixels.y());
23097 QPointF rotatedPos = inputTransform.map(pos);
23098 QFontMetrics fontMetrics(mFont);
23099 QRect textRect = fontMetrics.boundingRect(
23100 0, 0, 0, 0, Qt::TextDontClip | mTextAlignment, mText);
23101 QRect textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(),
23102 mPadding.right(), mPadding.bottom());
23103 QPointF textPos =
23104 getTextDrawPoint(positionPixels, textBoxRect, mPositionAlignment);
23105 textBoxRect.moveTopLeft(textPos.toPoint());
23106
23107 return rectSelectTest(textBoxRect, rotatedPos, true);
23108 }
23109
23110 /* inherits documentation from base class */
23111 void QCPItemText::draw(QCPPainter *painter) {
23112 QPointF pos(position->pixelPoint());
23113 QTransform transform = painter->transform();
23114 transform.translate(pos.x(), pos.y());
23115 if (!qFuzzyIsNull(mRotation)) transform.rotate(mRotation);
23116 painter->setFont(mainFont());
23117 QRect textRect = painter->fontMetrics().boundingRect(
23118 0, 0, 0, 0, Qt::TextDontClip | mTextAlignment, mText);
23119 QRect textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(),
23120 mPadding.right(), mPadding.bottom());
23121 QPointF textPos = getTextDrawPoint(
23122 QPointF(0, 0), textBoxRect,
23123 mPositionAlignment); // 0, 0 because the transform does the translation
23124 textRect.moveTopLeft(textPos.toPoint() +
23125 QPoint(mPadding.left(), mPadding.top()));
23126 textBoxRect.moveTopLeft(textPos.toPoint());
23127 double clipPad = mainPen().widthF();
23128 QRect boundingRect =
23129 textBoxRect.adjusted(-clipPad, -clipPad, clipPad, clipPad);
23130 if (transform.mapRect(boundingRect)
23131 .intersects(painter->transform().mapRect(clipRect()))) {
23132 painter->setTransform(transform);
23133 if ((mainBrush().style() != Qt::NoBrush &&
23134 mainBrush().color().alpha() != 0) ||
23135 (mainPen().style() != Qt::NoPen && mainPen().color().alpha() != 0)) {
23136 painter->setPen(mainPen());
23137 painter->setBrush(mainBrush());
23138 painter->drawRect(textBoxRect);
23139 }
23140 painter->setBrush(Qt::NoBrush);
23141 painter->setPen(QPen(mainColor()));
23142 painter->drawText(textRect, Qt::TextDontClip | mTextAlignment, mText);
23143 }
23144 }
23145
23146 /* inherits documentation from base class */
23147 QPointF QCPItemText::anchorPixelPoint(int anchorId) const {
23148 // get actual rect points (pretty much copied from draw function):
23149 QPointF pos(position->pixelPoint());
23150 QTransform transform;
23151 transform.translate(pos.x(), pos.y());
23152 if (!qFuzzyIsNull(mRotation)) transform.rotate(mRotation);
23153 QFontMetrics fontMetrics(mainFont());
23154 QRect textRect = fontMetrics.boundingRect(
23155 0, 0, 0, 0, Qt::TextDontClip | mTextAlignment, mText);
23156 QRectF textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(),
23157 mPadding.right(), mPadding.bottom());
23158 QPointF textPos = getTextDrawPoint(
23159 QPointF(0, 0), textBoxRect,
23160 mPositionAlignment); // 0, 0 because the transform does the translation
23161 textBoxRect.moveTopLeft(textPos.toPoint());
23162 QPolygonF rectPoly = transform.map(QPolygonF(textBoxRect));
23163
23164 switch (anchorId) {
23165 case aiTopLeft:
23166 return rectPoly.at(0);
23167 case aiTop:
23168 return (rectPoly.at(0) + rectPoly.at(1)) * 0.5;
23169 case aiTopRight:
23170 return rectPoly.at(1);
23171 case aiRight:
23172 return (rectPoly.at(1) + rectPoly.at(2)) * 0.5;
23173 case aiBottomRight:
23174 return rectPoly.at(2);
23175 case aiBottom:
23176 return (rectPoly.at(2) + rectPoly.at(3)) * 0.5;
23177 case aiBottomLeft:
23178 return rectPoly.at(3);
23179 case aiLeft:
23180 return (rectPoly.at(3) + rectPoly.at(0)) * 0.5;
23181 }
23182
23183 qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
23184 return QPointF();
23185 }
23186
23187 /*! \internal
23188
23189 Returns the point that must be given to the QPainter::drawText function (which
23190 expects the top left point of the text rect), according to the position \a
23191 pos, the text bounding box \a rect and the requested \a positionAlignment.
23192
23193 For example, if \a positionAlignment is <tt>Qt::AlignLeft |
23194 Qt::AlignBottom</tt> the returned point will be shifted upward by the height
23195 of \a rect, starting from \a pos. So if the text is finally drawn at that
23196 point, the lower left corner of the resulting text rect is at \a pos.
23197 */
23198 QPointF QCPItemText::getTextDrawPoint(const QPointF &pos, const QRectF &rect,
23199 Qt::Alignment positionAlignment) const {
23200 if (positionAlignment == 0 ||
23201 positionAlignment == (Qt::AlignLeft | Qt::AlignTop))
23202 return pos;
23203
23204 QPointF result = pos; // start at top left
23205 if (positionAlignment.testFlag(Qt::AlignHCenter))
23206 result.rx() -= rect.width() / 2.0;
23207 else if (positionAlignment.testFlag(Qt::AlignRight))
23208 result.rx() -= rect.width();
23209 if (positionAlignment.testFlag(Qt::AlignVCenter))
23210 result.ry() -= rect.height() / 2.0;
23211 else if (positionAlignment.testFlag(Qt::AlignBottom))
23212 result.ry() -= rect.height();
23213 return result;
23214 }
23215
23216 /*! \internal
23217
23218 Returns the font that should be used for drawing text. Returns mFont when the
23219 item is not selected and mSelectedFont when it is.
23220 */
23221 QFont QCPItemText::mainFont() const {
23222 return mSelected ? mSelectedFont : mFont;
23223 }
23224
23225 /*! \internal
23226
23227 Returns the color that should be used for drawing text. Returns mColor when
23228 the item is not selected and mSelectedColor when it is.
23229 */
23230 QColor QCPItemText::mainColor() const {
23231 return mSelected ? mSelectedColor : mColor;
23232 }
23233
23234 /*! \internal
23235
23236 Returns the pen that should be used for drawing lines. Returns mPen when the
23237 item is not selected and mSelectedPen when it is.
23238 */
23239 QPen QCPItemText::mainPen() const { return mSelected ? mSelectedPen : mPen; }
23240
23241 /*! \internal
23242
23243 Returns the brush that should be used for drawing fills of the item. Returns
23244 mBrush when the item is not selected and mSelectedBrush when it is.
23245 */
23246 QBrush QCPItemText::mainBrush() const {
23247 return mSelected ? mSelectedBrush : mBrush;
23248 }
23249
23250 ////////////////////////////////////////////////////////////////////////////////////////////////////
23251 //////////////////// QCPItemEllipse
23252 ////////////////////////////////////////////////////////////////////////////////////////////////////
23253
23254 /*! \class QCPItemEllipse
23255 \brief An ellipse
23256
23257 \image html QCPItemEllipse.png "Ellipse example. Blue dotted circles are
23258 anchors, solid blue discs are positions."
23259
23260 It has two positions, \a topLeft and \a bottomRight, which define the rect the
23261 ellipse will be drawn in.
23262 */
23263
23264 /*!
23265 Creates an ellipse item and sets default values.
23266
23267 The constructed item can be added to the plot with QCustomPlot::addItem.
23268 */
23269 QCPItemEllipse::QCPItemEllipse(QCustomPlot *parentPlot)
23270 : QCPAbstractItem(parentPlot),
23271 topLeft(createPosition(QLatin1String("topLeft"))),
23272 bottomRight(createPosition(QLatin1String("bottomRight"))),
23273 topLeftRim(createAnchor(QLatin1String("topLeftRim"), aiTopLeftRim)),
23274 top(createAnchor(QLatin1String("top"), aiTop)),
23275 topRightRim(createAnchor(QLatin1String("topRightRim"), aiTopRightRim)),
23276 right(createAnchor(QLatin1String("right"), aiRight)),
23277 bottomRightRim(
23278 createAnchor(QLatin1String("bottomRightRim"), aiBottomRightRim)),
23279 bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
23280 bottomLeftRim(
23281 createAnchor(QLatin1String("bottomLeftRim"), aiBottomLeftRim)),
23282 left(createAnchor(QLatin1String("left"), aiLeft)),
23283 center(createAnchor(QLatin1String("center"), aiCenter)) {
23284 topLeft->setCoords(0, 1);
23285 bottomRight->setCoords(1, 0);
23286
23287 setPen(QPen(Qt::black));
23288 setSelectedPen(QPen(Qt::blue, 2));
23289 setBrush(Qt::NoBrush);
23290 setSelectedBrush(Qt::NoBrush);
23291 }
23292
23293 QCPItemEllipse::~QCPItemEllipse() {}
23294
23295 /*!
23296 Sets the pen that will be used to draw the line of the ellipse
23297
23298 \see setSelectedPen, setBrush
23299 */
23300 void QCPItemEllipse::setPen(const QPen &pen) { mPen = pen; }
23301
23302 /*!
23303 Sets the pen that will be used to draw the line of the ellipse when selected
23304
23305 \see setPen, setSelected
23306 */
23307 void QCPItemEllipse::setSelectedPen(const QPen &pen) { mSelectedPen = pen; }
23308
23309 /*!
23310 Sets the brush that will be used to fill the ellipse. To disable filling, set
23311 \a brush to Qt::NoBrush.
23312
23313 \see setSelectedBrush, setPen
23314 */
23315 void QCPItemEllipse::setBrush(const QBrush &brush) { mBrush = brush; }
23316
23317 /*!
23318 Sets the brush that will be used to fill the ellipse when selected. To disable
23319 filling, set \a brush to Qt::NoBrush.
23320
23321 \see setBrush
23322 */
23323 void QCPItemEllipse::setSelectedBrush(const QBrush &brush) {
23324 mSelectedBrush = brush;
23325 }
23326
23327 /* inherits documentation from base class */
23328 double QCPItemEllipse::selectTest(const QPointF &pos, bool onlySelectable,
23329 QVariant *details) const {
23330 Q_UNUSED(details)
23331 if (onlySelectable && !mSelectable) return -1;
23332
23333 double result = -1;
23334 QPointF p1 = topLeft->pixelPoint();
23335 QPointF p2 = bottomRight->pixelPoint();
23336 QPointF center((p1 + p2) / 2.0);
23337 double a = qAbs(p1.x() - p2.x()) / 2.0;
23338 double b = qAbs(p1.y() - p2.y()) / 2.0;
23339 double x = pos.x() - center.x();
23340 double y = pos.y() - center.y();
23341
23342 // distance to border:
23343 double c = 1.0 / qSqrt(x * x / (a * a) + y * y / (b * b));
23344 result = qAbs(c - 1) * qSqrt(x * x + y * y);
23345 // filled ellipse, allow click inside to count as hit:
23346 if (result > mParentPlot->selectionTolerance() * 0.99 &&
23347 mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0) {
23348 if (x * x / (a * a) + y * y / (b * b) <= 1)
23349 result = mParentPlot->selectionTolerance() * 0.99;
23350 }
23351 return result;
23352 }
23353
23354 /* inherits documentation from base class */
23355 void QCPItemEllipse::draw(QCPPainter *painter) {
23356 QPointF p1 = topLeft->pixelPoint();
23357 QPointF p2 = bottomRight->pixelPoint();
23358 if (p1.toPoint() == p2.toPoint()) return;
23359 QRectF ellipseRect = QRectF(p1, p2).normalized();
23360 QRect clip = clipRect().adjusted(-mainPen().widthF(), -mainPen().widthF(),
23361 mainPen().widthF(), mainPen().widthF());
23362 if (ellipseRect.intersects(clip)) // only draw if bounding rect of ellipse is
23363 // visible in cliprect
23364 {
23365 painter->setPen(mainPen());
23366 painter->setBrush(mainBrush());
23367 #ifdef __EXCEPTIONS
23368 try // drawEllipse sometimes throws exceptions if ellipse is too big
23369 {
23370 #endif
23371 painter->drawEllipse(ellipseRect);
23372 #ifdef __EXCEPTIONS
23373 } catch (...) {
23374 qDebug() << Q_FUNC_INFO << "Item too large for memory, setting invisible";
23375 setVisible(false);
23376 }
23377 #endif
23378 }
23379 }
23380
23381 /* inherits documentation from base class */
23382 QPointF QCPItemEllipse::anchorPixelPoint(int anchorId) const {
23383 QRectF rect = QRectF(topLeft->pixelPoint(), bottomRight->pixelPoint());
23384 switch (anchorId) {
23385 case aiTopLeftRim:
23386 return rect.center() + (rect.topLeft() - rect.center()) * 1 / qSqrt(2);
23387 case aiTop:
23388 return (rect.topLeft() + rect.topRight()) * 0.5;
23389 case aiTopRightRim:
23390 return rect.center() + (rect.topRight() - rect.center()) * 1 / qSqrt(2);
23391 case aiRight:
23392 return (rect.topRight() + rect.bottomRight()) * 0.5;
23393 case aiBottomRightRim:
23394 return rect.center() +
23395 (rect.bottomRight() - rect.center()) * 1 / qSqrt(2);
23396 case aiBottom:
23397 return (rect.bottomLeft() + rect.bottomRight()) * 0.5;
23398 case aiBottomLeftRim:
23399 return rect.center() + (rect.bottomLeft() - rect.center()) * 1 / qSqrt(2);
23400 case aiLeft:
23401 return (rect.topLeft() + rect.bottomLeft()) * 0.5;
23402 case aiCenter:
23403 return (rect.topLeft() + rect.bottomRight()) * 0.5;
23404 }
23405
23406 qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
23407 return QPointF();
23408 }
23409
23410 /*! \internal
23411
23412 Returns the pen that should be used for drawing lines. Returns mPen when the
23413 item is not selected and mSelectedPen when it is.
23414 */
23415 QPen QCPItemEllipse::mainPen() const { return mSelected ? mSelectedPen : mPen; }
23416
23417 /*! \internal
23418
23419 Returns the brush that should be used for drawing fills of the item. Returns
23420 mBrush when the item is not selected and mSelectedBrush when it is.
23421 */
23422 QBrush QCPItemEllipse::mainBrush() const {
23423 return mSelected ? mSelectedBrush : mBrush;
23424 }
23425
23426 ////////////////////////////////////////////////////////////////////////////////////////////////////
23427 //////////////////// QCPItemPixmap
23428 ////////////////////////////////////////////////////////////////////////////////////////////////////
23429
23430 /*! \class QCPItemPixmap
23431 \brief An arbitrary pixmap
23432
23433 \image html QCPItemPixmap.png "Pixmap example. Blue dotted circles are
23434 anchors, solid blue discs are positions."
23435
23436 It has two positions, \a topLeft and \a bottomRight, which define the
23437 rectangle the pixmap will be drawn in. Depending on the scale setting (\ref
23438 setScaled), the pixmap will be either scaled to fit the rectangle or be drawn
23439 aligned to the topLeft position.
23440
23441 If scaling is enabled and \a topLeft is further to the bottom/right than \a
23442 bottomRight (as shown on the right side of the example image), the pixmap will
23443 be flipped in the respective orientations.
23444 */
23445
23446 /*!
23447 Creates a rectangle item and sets default values.
23448
23449 The constructed item can be added to the plot with QCustomPlot::addItem.
23450 */
23451 QCPItemPixmap::QCPItemPixmap(QCustomPlot *parentPlot)
23452 : QCPAbstractItem(parentPlot),
23453 topLeft(createPosition(QLatin1String("topLeft"))),
23454 bottomRight(createPosition(QLatin1String("bottomRight"))),
23455 top(createAnchor(QLatin1String("top"), aiTop)),
23456 topRight(createAnchor(QLatin1String("topRight"), aiTopRight)),
23457 right(createAnchor(QLatin1String("right"), aiRight)),
23458 bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
23459 bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)),
23460 left(createAnchor(QLatin1String("left"), aiLeft)),
23461 mScaledPixmapInvalidated(true) {
23462 topLeft->setCoords(0, 1);
23463 bottomRight->setCoords(1, 0);
23464
23465 setPen(Qt::NoPen);
23466 setSelectedPen(QPen(Qt::blue));
23467 setScaled(false, Qt::KeepAspectRatio, Qt::SmoothTransformation);
23468 }
23469
23470 QCPItemPixmap::~QCPItemPixmap() {}
23471
23472 /*!
23473 Sets the pixmap that will be displayed.
23474 */
23475 void QCPItemPixmap::setPixmap(const QPixmap &pixmap) {
23476 mPixmap = pixmap;
23477 mScaledPixmapInvalidated = true;
23478 if (mPixmap.isNull()) qDebug() << Q_FUNC_INFO << "pixmap is null";
23479 }
23480
23481 /*!
23482 Sets whether the pixmap will be scaled to fit the rectangle defined by the \a
23483 topLeft and \a bottomRight positions.
23484 */
23485 void QCPItemPixmap::setScaled(bool scaled, Qt::AspectRatioMode aspectRatioMode,
23486 Qt::TransformationMode transformationMode) {
23487 mScaled = scaled;
23488 mAspectRatioMode = aspectRatioMode;
23489 mTransformationMode = transformationMode;
23490 mScaledPixmapInvalidated = true;
23491 }
23492
23493 /*!
23494 Sets the pen that will be used to draw a border around the pixmap.
23495
23496 \see setSelectedPen, setBrush
23497 */
23498 void QCPItemPixmap::setPen(const QPen &pen) { mPen = pen; }
23499
23500 /*!
23501 Sets the pen that will be used to draw a border around the pixmap when
23502 selected
23503
23504 \see setPen, setSelected
23505 */
23506 void QCPItemPixmap::setSelectedPen(const QPen &pen) { mSelectedPen = pen; }
23507
23508 /* inherits documentation from base class */
23509 double QCPItemPixmap::selectTest(const QPointF &pos, bool onlySelectable,
23510 QVariant *details) const {
23511 Q_UNUSED(details)
23512 if (onlySelectable && !mSelectable) return -1;
23513
23514 return rectSelectTest(getFinalRect(), pos, true);
23515 }
23516
23517 /* inherits documentation from base class */
23518 void QCPItemPixmap::draw(QCPPainter *painter) {
23519 bool flipHorz = false;
23520 bool flipVert = false;
23521 QRect rect = getFinalRect(&flipHorz, &flipVert);
23522 double clipPad = mainPen().style() == Qt::NoPen ? 0 : mainPen().widthF();
23523 QRect boundingRect = rect.adjusted(-clipPad, -clipPad, clipPad, clipPad);
23524 if (boundingRect.intersects(clipRect())) {
23525 updateScaledPixmap(rect, flipHorz, flipVert);
23526 painter->drawPixmap(rect.topLeft(), mScaled ? mScaledPixmap : mPixmap);
23527 QPen pen = mainPen();
23528 if (pen.style() != Qt::NoPen) {
23529 painter->setPen(pen);
23530 painter->setBrush(Qt::NoBrush);
23531 painter->drawRect(rect);
23532 }
23533 }
23534 }
23535
23536 /* inherits documentation from base class */
23537 QPointF QCPItemPixmap::anchorPixelPoint(int anchorId) const {
23538 bool flipHorz;
23539 bool flipVert;
23540 QRect rect = getFinalRect(&flipHorz, &flipVert);
23541 // we actually want denormal rects (negative width/height) here, so restore
23542 // the flipped state:
23543 if (flipHorz) rect.adjust(rect.width(), 0, -rect.width(), 0);
23544 if (flipVert) rect.adjust(0, rect.height(), 0, -rect.height());
23545
23546 switch (anchorId) {
23547 case aiTop:
23548 return (rect.topLeft() + rect.topRight()) * 0.5;
23549 case aiTopRight:
23550 return rect.topRight();
23551 case aiRight:
23552 return (rect.topRight() + rect.bottomRight()) * 0.5;
23553 case aiBottom:
23554 return (rect.bottomLeft() + rect.bottomRight()) * 0.5;
23555 case aiBottomLeft:
23556 return rect.bottomLeft();
23557 case aiLeft:
23558 return (rect.topLeft() + rect.bottomLeft()) * 0.5;
23559 ;
23560 }
23561
23562 qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
23563 return QPointF();
23564 }
23565
23566 /*! \internal
23567
23568 Creates the buffered scaled image (\a mScaledPixmap) to fit the specified \a
23569 finalRect. The parameters \a flipHorz and \a flipVert control whether the
23570 resulting image shall be flipped horizontally or vertically. (This is used
23571 when \a topLeft is further to the bottom/right than \a bottomRight.)
23572
23573 This function only creates the scaled pixmap when the buffered pixmap has a
23574 different size than the expected result, so calling this function repeatedly,
23575 e.g. in the \ref draw function, does not cause expensive rescaling every time.
23576
23577 If scaling is disabled, sets mScaledPixmap to a null QPixmap.
23578 */
23579 void QCPItemPixmap::updateScaledPixmap(QRect finalRect, bool flipHorz,
23580 bool flipVert) {
23581 if (mPixmap.isNull()) return;
23582
23583 if (mScaled) {
23584 if (finalRect.isNull()) finalRect = getFinalRect(&flipHorz, &flipVert);
23585 if (mScaledPixmapInvalidated || finalRect.size() != mScaledPixmap.size()) {
23586 mScaledPixmap = mPixmap.scaled(finalRect.size(), mAspectRatioMode,
23587 mTransformationMode);
23588 if (flipHorz || flipVert)
23589 mScaledPixmap = QPixmap::fromImage(
23590 mScaledPixmap.toImage().mirrored(flipHorz, flipVert));
23591 }
23592 } else if (!mScaledPixmap.isNull())
23593 mScaledPixmap = QPixmap();
23594 mScaledPixmapInvalidated = false;
23595 }
23596
23597 /*! \internal
23598
23599 Returns the final (tight) rect the pixmap is drawn in, depending on the
23600 current item positions and scaling settings.
23601
23602 The output parameters \a flippedHorz and \a flippedVert return whether the
23603 pixmap should be drawn flipped horizontally or vertically in the returned
23604 rect. (The returned rect itself is always normalized, i.e. the top left corner
23605 of the rect is actually further to the top/left than the bottom right corner).
23606 This is the case when the item position \a topLeft is further to the
23607 bottom/right than \a bottomRight.
23608
23609 If scaling is disabled, returns a rect with size of the original pixmap and
23610 the top left corner aligned with the item position \a topLeft. The position \a
23611 bottomRight is ignored.
23612 */
23613 QRect QCPItemPixmap::getFinalRect(bool *flippedHorz, bool *flippedVert) const {
23614 QRect result;
23615 bool flipHorz = false;
23616 bool flipVert = false;
23617 QPoint p1 = topLeft->pixelPoint().toPoint();
23618 QPoint p2 = bottomRight->pixelPoint().toPoint();
23619 if (p1 == p2) return QRect(p1, QSize(0, 0));
23620 if (mScaled) {
23621 QSize newSize = QSize(p2.x() - p1.x(), p2.y() - p1.y());
23622 QPoint topLeft = p1;
23623 if (newSize.width() < 0) {
23624 flipHorz = true;
23625 newSize.rwidth() *= -1;
23626 topLeft.setX(p2.x());
23627 }
23628 if (newSize.height() < 0) {
23629 flipVert = true;
23630 newSize.rheight() *= -1;
23631 topLeft.setY(p2.y());
23632 }
23633 QSize scaledSize = mPixmap.size();
23634 scaledSize.scale(newSize, mAspectRatioMode);
23635 result = QRect(topLeft, scaledSize);
23636 } else {
23637 result = QRect(p1, mPixmap.size());
23638 }
23639 if (flippedHorz) *flippedHorz = flipHorz;
23640 if (flippedVert) *flippedVert = flipVert;
23641 return result;
23642 }
23643
23644 /*! \internal
23645
23646 Returns the pen that should be used for drawing lines. Returns mPen when the
23647 item is not selected and mSelectedPen when it is.
23648 */
23649 QPen QCPItemPixmap::mainPen() const { return mSelected ? mSelectedPen : mPen; }
23650
23651 ////////////////////////////////////////////////////////////////////////////////////////////////////
23652 //////////////////// QCPItemTracer
23653 ////////////////////////////////////////////////////////////////////////////////////////////////////
23654
23655 /*! \class QCPItemTracer
23656 \brief Item that sticks to QCPGraph data points
23657
23658 \image html QCPItemTracer.png "Tracer example. Blue dotted circles are
23659 anchors, solid blue discs are positions."
23660
23661 The tracer can be connected with a QCPGraph via \ref setGraph. Then it will
23662 automatically adopt the coordinate axes of the graph and update its \a
23663 position to be on the graph's data. This means the key stays controllable via
23664 \ref setGraphKey, but the value will follow the graph data. If a QCPGraph is
23665 connected, note that setting the coordinates of the tracer item directly via
23666 \a position will have no effect because they will be overriden in the next
23667 redraw (this is when the coordinate update happens).
23668
23669 If the specified key in \ref setGraphKey is outside the key bounds of the
23670 graph, the tracer will stay at the corresponding end of the graph.
23671
23672 With \ref setInterpolating you may specify whether the tracer may only stay
23673 exactly on data points or whether it interpolates data points linearly, if
23674 given a key that lies between two data points of the graph.
23675
23676 The tracer has different visual styles, see \ref setStyle. It is also possible
23677 to make the tracer have no own visual appearance (set the style to \ref
23678 tsNone), and just connect other item positions to the tracer \a position (used
23679 as an anchor) via \ref QCPItemPosition::setParentAnchor.
23680
23681 \note The tracer position is only automatically updated upon redraws. So when
23682 the data of the graph changes and immediately afterwards (without a redraw)
23683 the a position coordinates of the tracer are retrieved, they will not reflect
23684 the updated data of the graph. In this case \ref updatePosition must be called
23685 manually, prior to reading the tracer coordinates.
23686 */
23687
23688 /*!
23689 Creates a tracer item and sets default values.
23690
23691 The constructed item can be added to the plot with QCustomPlot::addItem.
23692 */
23693 QCPItemTracer::QCPItemTracer(QCustomPlot *parentPlot)
23694 : QCPAbstractItem(parentPlot),
23695 position(createPosition(QLatin1String("position"))),
23696 mGraph(0) {
23697 position->setCoords(0, 0);
23698
23699 setBrush(Qt::NoBrush);
23700 setSelectedBrush(Qt::NoBrush);
23701 setPen(QPen(Qt::black));
23702 setSelectedPen(QPen(Qt::blue, 2));
23703 setStyle(tsCrosshair);
23704 setSize(6);
23705 setInterpolating(false);
23706 setGraphKey(0);
23707 }
23708
23709 QCPItemTracer::~QCPItemTracer() {}
23710
23711 /*!
23712 Sets the pen that will be used to draw the line of the tracer
23713
23714 \see setSelectedPen, setBrush
23715 */
23716 void QCPItemTracer::setPen(const QPen &pen) { mPen = pen; }
23717
23718 /*!
23719 Sets the pen that will be used to draw the line of the tracer when selected
23720
23721 \see setPen, setSelected
23722 */
23723 void QCPItemTracer::setSelectedPen(const QPen &pen) { mSelectedPen = pen; }
23724
23725 /*!
23726 Sets the brush that will be used to draw any fills of the tracer
23727
23728 \see setSelectedBrush, setPen
23729 */
23730 void QCPItemTracer::setBrush(const QBrush &brush) { mBrush = brush; }
23731
23732 /*!
23733 Sets the brush that will be used to draw any fills of the tracer, when
23734 selected.
23735
23736 \see setBrush, setSelected
23737 */
23738 void QCPItemTracer::setSelectedBrush(const QBrush &brush) {
23739 mSelectedBrush = brush;
23740 }
23741
23742 /*!
23743 Sets the size of the tracer in pixels, if the style supports setting a size
23744 (e.g. \ref tsSquare does, \ref tsCrosshair does not).
23745 */
23746 void QCPItemTracer::setSize(double size) { mSize = size; }
23747
23748 /*!
23749 Sets the style/visual appearance of the tracer.
23750
23751 If you only want to use the tracer \a position as an anchor for other items,
23752 set \a style to \ref tsNone.
23753 */
23754 void QCPItemTracer::setStyle(QCPItemTracer::TracerStyle style) {
23755 mStyle = style;
23756 }
23757
23758 /*!
23759 Sets the QCPGraph this tracer sticks to. The tracer \a position will be set to
23760 type QCPItemPosition::ptPlotCoords and the axes will be set to the axes of \a
23761 graph.
23762
23763 To free the tracer from any graph, set \a graph to 0. The tracer \a position
23764 can then be placed freely like any other item position. This is the state the
23765 tracer will assume when its graph gets deleted while still attached to it.
23766
23767 \see setGraphKey
23768 */
23769 void QCPItemTracer::setGraph(QCPGraph *graph) {
23770 if (graph) {
23771 if (graph->parentPlot() == mParentPlot) {
23772 position->setType(QCPItemPosition::ptPlotCoords);
23773 position->setAxes(graph->keyAxis(), graph->valueAxis());
23774 mGraph = graph;
23775 updatePosition();
23776 } else
23777 qDebug() << Q_FUNC_INFO
23778 << "graph isn't in same QCustomPlot instance as this item";
23779 } else {
23780 mGraph = 0;
23781 }
23782 }
23783
23784 /*!
23785 Sets the key of the graph's data point the tracer will be positioned at. This
23786 is the only free coordinate of a tracer when attached to a graph.
23787
23788 Depending on \ref setInterpolating, the tracer will be either positioned on
23789 the data point closest to \a key, or will stay exactly at \a key and
23790 interpolate the value linearly.
23791
23792 \see setGraph, setInterpolating
23793 */
23794 void QCPItemTracer::setGraphKey(double key) { mGraphKey = key; }
23795
23796 /*!
23797 Sets whether the value of the graph's data points shall be interpolated, when
23798 positioning the tracer.
23799
23800 If \a enabled is set to false and a key is given with \ref setGraphKey, the
23801 tracer is placed on the data point of the graph which is closest to the key,
23802 but which is not necessarily exactly there. If \a enabled is true, the tracer
23803 will be positioned exactly at the specified key, and the appropriate value
23804 will be interpolated from the graph's data points linearly.
23805
23806 \see setGraph, setGraphKey
23807 */
23808 void QCPItemTracer::setInterpolating(bool enabled) { mInterpolating = enabled; }
23809
23810 /* inherits documentation from base class */
23811 double QCPItemTracer::selectTest(const QPointF &pos, bool onlySelectable,
23812 QVariant *details) const {
23813 Q_UNUSED(details)
23814 if (onlySelectable && !mSelectable) return -1;
23815
23816 QPointF center(position->pixelPoint());
23817 double w = mSize / 2.0;
23818 QRect clip = clipRect();
23819 switch (mStyle) {
23820 case tsNone:
23821 return -1;
23822 case tsPlus: {
23823 if (clipRect().intersects(
23824 QRectF(center - QPointF(w, w), center + QPointF(w, w)).toRect()))
23825 return qSqrt(qMin(
23826 distSqrToLine(center + QPointF(-w, 0), center + QPointF(w, 0), pos),
23827 distSqrToLine(center + QPointF(0, -w), center + QPointF(0, w),
23828 pos)));
23829 break;
23830 }
23831 case tsCrosshair: {
23832 return qSqrt(
23833 qMin(distSqrToLine(QPointF(clip.left(), center.y()),
23834 QPointF(clip.right(), center.y()), pos),
23835 distSqrToLine(QPointF(center.x(), clip.top()),
23836 QPointF(center.x(), clip.bottom()), pos)));
23837 }
23838 case tsCircle: {
23839 if (clip.intersects(QRectF(center - QPointF(w, w), center + QPointF(w, w))
23840 .toRect())) {
23841 // distance to border:
23842 double centerDist = QVector2D(center - pos).length();
23843 double circleLine = w;
23844 double result = qAbs(centerDist - circleLine);
23845 // filled ellipse, allow click inside to count as hit:
23846 if (result > mParentPlot->selectionTolerance() * 0.99 &&
23847 mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0) {
23848 if (centerDist <= circleLine)
23849 result = mParentPlot->selectionTolerance() * 0.99;
23850 }
23851 return result;
23852 }
23853 break;
23854 }
23855 case tsSquare: {
23856 if (clip.intersects(QRectF(center - QPointF(w, w), center + QPointF(w, w))
23857 .toRect())) {
23858 QRectF rect = QRectF(center - QPointF(w, w), center + QPointF(w, w));
23859 bool filledRect =
23860 mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0;
23861 return rectSelectTest(rect, pos, filledRect);
23862 }
23863 break;
23864 }
23865 }
23866 return -1;
23867 }
23868
23869 /* inherits documentation from base class */
23870 void QCPItemTracer::draw(QCPPainter *painter) {
23871 updatePosition();
23872 if (mStyle == tsNone) return;
23873
23874 painter->setPen(mainPen());
23875 painter->setBrush(mainBrush());
23876 QPointF center(position->pixelPoint());
23877 double w = mSize / 2.0;
23878 QRect clip = clipRect();
23879 switch (mStyle) {
23880 case tsNone:
23881 return;
23882 case tsPlus: {
23883 if (clip.intersects(QRectF(center - QPointF(w, w), center + QPointF(w, w))
23884 .toRect())) {
23885 painter->drawLine(
23886 QLineF(center + QPointF(-w, 0), center + QPointF(w, 0)));
23887 painter->drawLine(
23888 QLineF(center + QPointF(0, -w), center + QPointF(0, w)));
23889 }
23890 break;
23891 }
23892 case tsCrosshair: {
23893 if (center.y() > clip.top() && center.y() < clip.bottom())
23894 painter->drawLine(
23895 QLineF(clip.left(), center.y(), clip.right(), center.y()));
23896 if (center.x() > clip.left() && center.x() < clip.right())
23897 painter->drawLine(
23898 QLineF(center.x(), clip.top(), center.x(), clip.bottom()));
23899 break;
23900 }
23901 case tsCircle: {
23902 if (clip.intersects(
23903 QRectF(center - QPointF(w, w), center + QPointF(w, w)).toRect()))
23904 painter->drawEllipse(center, w, w);
23905 break;
23906 }
23907 case tsSquare: {
23908 if (clip.intersects(
23909 QRectF(center - QPointF(w, w), center + QPointF(w, w)).toRect()))
23910 painter->drawRect(
23911 QRectF(center - QPointF(w, w), center + QPointF(w, w)));
23912 break;
23913 }
23914 }
23915 }
23916
23917 /*!
23918 If the tracer is connected with a graph (\ref setGraph), this function updates
23919 the tracer's \a position to reside on the graph data, depending on the
23920 configured key (\ref setGraphKey).
23921
23922 It is called automatically on every redraw and normally doesn't need to be
23923 called manually. One exception is when you want to read the tracer coordinates
23924 via \a position and are not sure that the graph's data (or the tracer key with
23925 \ref setGraphKey) hasn't changed since the last redraw. In that situation,
23926 call this function before accessing \a position, to make sure you don't get
23927 out-of-date coordinates.
23928
23929 If there is no graph set on this tracer, this function does nothing.
23930 */
23931 void QCPItemTracer::updatePosition() {
23932 if (mGraph) {
23933 if (mParentPlot->hasPlottable(mGraph)) {
23934 if (mGraph->data()->size() > 1) {
23935 QCPDataMap::const_iterator first = mGraph->data()->constBegin();
23936 QCPDataMap::const_iterator last = mGraph->data()->constEnd() - 1;
23937 if (mGraphKey < first.key())
23938 position->setCoords(first.key(), first.value().value);
23939 else if (mGraphKey > last.key())
23940 position->setCoords(last.key(), last.value().value);
23941 else {
23942 QCPDataMap::const_iterator it = mGraph->data()->lowerBound(mGraphKey);
23943 if (it != first) // mGraphKey is somewhere between iterators
23944 {
23945 QCPDataMap::const_iterator prevIt = it - 1;
23946 if (mInterpolating) {
23947 // interpolate between iterators around mGraphKey:
23948 double slope = 0;
23949 if (!qFuzzyCompare((double)it.key(), (double)prevIt.key()))
23950 slope = (it.value().value - prevIt.value().value) /
23951 (it.key() - prevIt.key());
23952 position->setCoords(
23953 mGraphKey,
23954 (mGraphKey - prevIt.key()) * slope + prevIt.value().value);
23955 } else {
23956 // find iterator with key closest to mGraphKey:
23957 if (mGraphKey < (prevIt.key() + it.key()) * 0.5) it = prevIt;
23958 position->setCoords(it.key(), it.value().value);
23959 }
23960 } else // mGraphKey is exactly on first iterator
23961 position->setCoords(it.key(), it.value().value);
23962 }
23963 } else if (mGraph->data()->size() == 1) {
23964 QCPDataMap::const_iterator it = mGraph->data()->constBegin();
23965 position->setCoords(it.key(), it.value().value);
23966 } else
23967 qDebug() << Q_FUNC_INFO << "graph has no data";
23968 } else
23969 qDebug() << Q_FUNC_INFO
23970 << "graph not contained in QCustomPlot instance (anymore)";
23971 }
23972 }
23973
23974 /*! \internal
23975
23976 Returns the pen that should be used for drawing lines. Returns mPen when the
23977 item is not selected and mSelectedPen when it is.
23978 */
23979 QPen QCPItemTracer::mainPen() const { return mSelected ? mSelectedPen : mPen; }
23980
23981 /*! \internal
23982
23983 Returns the brush that should be used for drawing fills of the item. Returns
23984 mBrush when the item is not selected and mSelectedBrush when it is.
23985 */
23986 QBrush QCPItemTracer::mainBrush() const {
23987 return mSelected ? mSelectedBrush : mBrush;
23988 }
23989
23990 ////////////////////////////////////////////////////////////////////////////////////////////////////
23991 //////////////////// QCPItemBracket
23992 ////////////////////////////////////////////////////////////////////////////////////////////////////
23993
23994 /*! \class QCPItemBracket
23995 \brief A bracket for referencing/highlighting certain parts in the plot.
23996
23997 \image html QCPItemBracket.png "Bracket example. Blue dotted circles are
23998 anchors, solid blue discs are positions."
23999
24000 It has two positions, \a left and \a right, which define the span of the
24001 bracket. If \a left is actually farther to the left than \a right, the bracket
24002 is opened to the bottom, as shown in the example image.
24003
24004 The bracket supports multiple styles via \ref setStyle. The length, i.e. how
24005 far the bracket stretches away from the embraced span, can be controlled with
24006 \ref setLength.
24007
24008 \image html QCPItemBracket-length.png
24009 <center>Demonstrating the effect of different values for \ref setLength, for
24010 styles \ref bsCalligraphic and \ref bsSquare. Anchors and positions are
24011 displayed for reference.</center>
24012
24013 It provides an anchor \a center, to allow connection of other items, e.g. an
24014 arrow (QCPItemLine or QCPItemCurve) or a text label (QCPItemText), to the
24015 bracket.
24016 */
24017
24018 /*!
24019 Creates a bracket item and sets default values.
24020
24021 The constructed item can be added to the plot with QCustomPlot::addItem.
24022 */
24023 QCPItemBracket::QCPItemBracket(QCustomPlot *parentPlot)
24024 : QCPAbstractItem(parentPlot),
24025 left(createPosition(QLatin1String("left"))),
24026 right(createPosition(QLatin1String("right"))),
24027 center(createAnchor(QLatin1String("center"), aiCenter)) {
24028 left->setCoords(0, 0);
24029 right->setCoords(1, 1);
24030
24031 setPen(QPen(Qt::black));
24032 setSelectedPen(QPen(Qt::blue, 2));
24033 setLength(8);
24034 setStyle(bsCalligraphic);
24035 }
24036
24037 QCPItemBracket::~QCPItemBracket() {}
24038
24039 /*!
24040 Sets the pen that will be used to draw the bracket.
24041
24042 Note that when the style is \ref bsCalligraphic, only the color will be taken
24043 from the pen, the stroke and width are ignored. To change the apparent stroke
24044 width of a calligraphic bracket, use \ref setLength, which has a similar
24045 effect.
24046
24047 \see setSelectedPen
24048 */
24049 void QCPItemBracket::setPen(const QPen &pen) { mPen = pen; }
24050
24051 /*!
24052 Sets the pen that will be used to draw the bracket when selected
24053
24054 \see setPen, setSelected
24055 */
24056 void QCPItemBracket::setSelectedPen(const QPen &pen) { mSelectedPen = pen; }
24057
24058 /*!
24059 Sets the \a length in pixels how far the bracket extends in the direction
24060 towards the embraced span of the bracket (i.e. perpendicular to the
24061 <i>left</i>-<i>right</i>-direction)
24062
24063 \image html QCPItemBracket-length.png
24064 <center>Demonstrating the effect of different values for \ref setLength, for
24065 styles \ref bsCalligraphic and \ref bsSquare. Anchors and positions are
24066 displayed for reference.</center>
24067 */
24068 void QCPItemBracket::setLength(double length) { mLength = length; }
24069
24070 /*!
24071 Sets the style of the bracket, i.e. the shape/visual appearance.
24072
24073 \see setPen
24074 */
24075 void QCPItemBracket::setStyle(QCPItemBracket::BracketStyle style) {
24076 mStyle = style;
24077 }
24078
24079 /* inherits documentation from base class */
24080 double QCPItemBracket::selectTest(const QPointF &pos, bool onlySelectable,
24081 QVariant *details) const {
24082 Q_UNUSED(details)
24083 if (onlySelectable && !mSelectable) return -1;
24084
24085 QVector2D leftVec(left->pixelPoint());
24086 QVector2D rightVec(right->pixelPoint());
24087 if (leftVec.toPoint() == rightVec.toPoint()) return -1;
24088
24089 QVector2D widthVec = (rightVec - leftVec) * 0.5f;
24090 QVector2D lengthVec(-widthVec.y(), widthVec.x());
24091 lengthVec = lengthVec.normalized() * mLength;
24092 QVector2D centerVec = (rightVec + leftVec) * 0.5f - lengthVec;
24093
24094 switch (mStyle) {
24095 case QCPItemBracket::bsSquare:
24096 case QCPItemBracket::bsRound: {
24097 double a = distSqrToLine((centerVec - widthVec).toPointF(),
24098 (centerVec + widthVec).toPointF(), pos);
24099 double b = distSqrToLine((centerVec - widthVec + lengthVec).toPointF(),
24100 (centerVec - widthVec).toPointF(), pos);
24101 double c = distSqrToLine((centerVec + widthVec + lengthVec).toPointF(),
24102 (centerVec + widthVec).toPointF(), pos);
24103 return qSqrt(qMin(qMin(a, b), c));
24104 }
24105 case QCPItemBracket::bsCurly:
24106 case QCPItemBracket::bsCalligraphic: {
24107 double a = distSqrToLine(
24108 (centerVec - widthVec * 0.75f + lengthVec * 0.15f).toPointF(),
24109 (centerVec + lengthVec * 0.3f).toPointF(), pos);
24110 double b = distSqrToLine(
24111 (centerVec - widthVec + lengthVec * 0.7f).toPointF(),
24112 (centerVec - widthVec * 0.75f + lengthVec * 0.15f).toPointF(), pos);
24113 double c = distSqrToLine(
24114 (centerVec + widthVec * 0.75f + lengthVec * 0.15f).toPointF(),
24115 (centerVec + lengthVec * 0.3f).toPointF(), pos);
24116 double d = distSqrToLine(
24117 (centerVec + widthVec + lengthVec * 0.7f).toPointF(),
24118 (centerVec + widthVec * 0.75f + lengthVec * 0.15f).toPointF(), pos);
24119 return qSqrt(qMin(qMin(a, b), qMin(c, d)));
24120 }
24121 }
24122 return -1;
24123 }
24124
24125 /* inherits documentation from base class */
24126 void QCPItemBracket::draw(QCPPainter *painter) {
24127 QVector2D leftVec(left->pixelPoint());
24128 QVector2D rightVec(right->pixelPoint());
24129 if (leftVec.toPoint() == rightVec.toPoint()) return;
24130
24131 QVector2D widthVec = (rightVec - leftVec) * 0.5f;
24132 QVector2D lengthVec(-widthVec.y(), widthVec.x());
24133 lengthVec = lengthVec.normalized() * mLength;
24134 QVector2D centerVec = (rightVec + leftVec) * 0.5f - lengthVec;
24135
24136 QPolygon boundingPoly;
24137 boundingPoly << leftVec.toPoint() << rightVec.toPoint()
24138 << (rightVec - lengthVec).toPoint()
24139 << (leftVec - lengthVec).toPoint();
24140 QRect clip = clipRect().adjusted(-mainPen().widthF(), -mainPen().widthF(),
24141 mainPen().widthF(), mainPen().widthF());
24142 if (clip.intersects(boundingPoly.boundingRect())) {
24143 painter->setPen(mainPen());
24144 switch (mStyle) {
24145 case bsSquare: {
24146 painter->drawLine((centerVec + widthVec).toPointF(),
24147 (centerVec - widthVec).toPointF());
24148 painter->drawLine((centerVec + widthVec).toPointF(),
24149 (centerVec + widthVec + lengthVec).toPointF());
24150 painter->drawLine((centerVec - widthVec).toPointF(),
24151 (centerVec - widthVec + lengthVec).toPointF());
24152 break;
24153 }
24154 case bsRound: {
24155 painter->setBrush(Qt::NoBrush);
24156 QPainterPath path;
24157 path.moveTo((centerVec + widthVec + lengthVec).toPointF());
24158 path.cubicTo((centerVec + widthVec).toPointF(),
24159 (centerVec + widthVec).toPointF(), centerVec.toPointF());
24160 path.cubicTo((centerVec - widthVec).toPointF(),
24161 (centerVec - widthVec).toPointF(),
24162 (centerVec - widthVec + lengthVec).toPointF());
24163 painter->drawPath(path);
24164 break;
24165 }
24166 case bsCurly: {
24167 painter->setBrush(Qt::NoBrush);
24168 QPainterPath path;
24169 path.moveTo((centerVec + widthVec + lengthVec).toPointF());
24170 path.cubicTo((centerVec + widthVec - lengthVec * 0.8f).toPointF(),
24171 (centerVec + 0.4f * widthVec + lengthVec).toPointF(),
24172 centerVec.toPointF());
24173 path.cubicTo((centerVec - 0.4f * widthVec + lengthVec).toPointF(),
24174 (centerVec - widthVec - lengthVec * 0.8f).toPointF(),
24175 (centerVec - widthVec + lengthVec).toPointF());
24176 painter->drawPath(path);
24177 break;
24178 }
24179 case bsCalligraphic: {
24180 painter->setPen(Qt::NoPen);
24181 painter->setBrush(QBrush(mainPen().color()));
24182 QPainterPath path;
24183 path.moveTo((centerVec + widthVec + lengthVec).toPointF());
24184
24185 path.cubicTo(
24186 (centerVec + widthVec - lengthVec * 0.8f).toPointF(),
24187 (centerVec + 0.4f * widthVec + 0.8f * lengthVec).toPointF(),
24188 centerVec.toPointF());
24189 path.cubicTo(
24190 (centerVec - 0.4f * widthVec + 0.8f * lengthVec).toPointF(),
24191 (centerVec - widthVec - lengthVec * 0.8f).toPointF(),
24192 (centerVec - widthVec + lengthVec).toPointF());
24193
24194 path.cubicTo(
24195 (centerVec - widthVec - lengthVec * 0.5f).toPointF(),
24196 (centerVec - 0.2f * widthVec + 1.2f * lengthVec).toPointF(),
24197 (centerVec + lengthVec * 0.2f).toPointF());
24198 path.cubicTo(
24199 (centerVec + 0.2f * widthVec + 1.2f * lengthVec).toPointF(),
24200 (centerVec + widthVec - lengthVec * 0.5f).toPointF(),
24201 (centerVec + widthVec + lengthVec).toPointF());
24202
24203 painter->drawPath(path);
24204 break;
24205 }
24206 }
24207 }
24208 }
24209
24210 /* inherits documentation from base class */
24211 QPointF QCPItemBracket::anchorPixelPoint(int anchorId) const {
24212 QVector2D leftVec(left->pixelPoint());
24213 QVector2D rightVec(right->pixelPoint());
24214 if (leftVec.toPoint() == rightVec.toPoint()) return leftVec.toPointF();
24215
24216 QVector2D widthVec = (rightVec - leftVec) * 0.5f;
24217 QVector2D lengthVec(-widthVec.y(), widthVec.x());
24218 lengthVec = lengthVec.normalized() * mLength;
24219 QVector2D centerVec = (rightVec + leftVec) * 0.5f - lengthVec;
24220
24221 switch (anchorId) {
24222 case aiCenter:
24223 return centerVec.toPointF();
24224 }
24225 qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
24226 return QPointF();
24227 }
24228
24229 /*! \internal
24230
24231 Returns the pen that should be used for drawing lines. Returns mPen when the
24232 item is not selected and mSelectedPen when it is.
24233 */
24234 QPen QCPItemBracket::mainPen() const { return mSelected ? mSelectedPen : mPen; }
24235