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 ¬AntialiasedElements) { |
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 |
|
|
|