GCC Code Coverage Report


Directory: ./
File: plugins/pyqcustomplot/qcustomplot.cpp
Date: 2025-04-14 11:18:00
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