1 | /* -*- C++ -*- |
2 | This file is part of the KDE libraries |
3 | SPDX-FileCopyrightText: 2003 Jason Harris <kstars@30doradus.org> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.0-or-later |
6 | */ |
7 | |
8 | #ifndef KPLOTWIDGET_H |
9 | #define KPLOTWIDGET_H |
10 | |
11 | #include <kplotting_export.h> |
12 | |
13 | #include <QFrame> |
14 | #include <QList> |
15 | |
16 | #include <memory> |
17 | |
18 | class KPlotAxis; |
19 | class KPlotObject; |
20 | class KPlotPoint; |
21 | |
22 | /*! |
23 | * \class KPlotWidget |
24 | * \inmodule KPlotting |
25 | * |
26 | * \brief Generic data plotting widget. |
27 | * |
28 | * Widget for drawing plots. The basic idea behind KPlotWidget is that |
29 | * you don't have to worry about any transformation from your data's |
30 | * natural units to screen pixel coordinates; this is handled internally |
31 | * by the widget. |
32 | * |
33 | * Data to be plotted are represented by one or more instances of |
34 | * KPlotObject. KPlotObject contains a list of QPointFs to be plotted |
35 | * (again, in the data's natural units), as well as information about how |
36 | * the data are to be rendered in the plot (i.e., as separate points or |
37 | * connected by lines? With what color and point style? etc). See |
38 | * KPlotObject for more information. |
39 | * |
40 | * KPlotWidget automatically adds axis labels with tickmarks and tick |
41 | * labels. These are encapsulated in the KPlotAxis class. All you have |
42 | * to do is set the limits of the plotting area in data units, and |
43 | * KPlotWidget will figure out the optimal positions and labels for the |
44 | * tickmarks on the axes. |
45 | * |
46 | * Example of usage: |
47 | * |
48 | * \code |
49 | * KPlotWidget *kpw = new KPlotWidget( parent ); |
50 | * // setting our limits for the plot |
51 | * kpw->setLimits( 1.0, 5.0, 1.0, 25.0 ); |
52 | * |
53 | * // creating a plot object whose points are connected by red lines ... |
54 | * KPlotObject *kpo = new KPlotObject( Qt::red, KPlotObject::Lines ); |
55 | * // ... adding some points to it ... |
56 | * for ( float x = 1.0; x <= 5.0; x += 0.1 ) |
57 | * kpo->addPoint( x, x*x ); |
58 | * |
59 | * // ... and adding the object to the plot widget |
60 | * kpw->addPlotObject( kpo ); |
61 | * \endcode |
62 | * |
63 | * \note KPlotWidget will take ownership of the objects added to it, so when |
64 | * clearing the objects list (eg with removeAllPlotObjects()) any previous |
65 | * reference to a KPlotObject already added to a KPlotWidget will be invalid. |
66 | * You can disable this behavior by using setAutoDelete(false). |
67 | */ |
68 | class KPLOTTING_EXPORT KPlotWidget : public QFrame |
69 | { |
70 | Q_OBJECT |
71 | |
72 | /*! |
73 | * \property KPlotWidget::leftPadding |
74 | */ |
75 | Q_PROPERTY(int leftPadding READ leftPadding) |
76 | |
77 | /*! |
78 | * \property KPlotWidget::rightPadding |
79 | */ |
80 | Q_PROPERTY(int rightPadding READ rightPadding) |
81 | |
82 | /*! |
83 | * \property KPlotWidget::topPadding |
84 | */ |
85 | Q_PROPERTY(int topPadding READ topPadding) |
86 | |
87 | /*! |
88 | * \property KPlotWidget::bottomPadding |
89 | */ |
90 | Q_PROPERTY(int bottomPadding READ bottomPadding) |
91 | |
92 | /*! |
93 | * \property KPlotWidget::backgroundColor |
94 | */ |
95 | Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor) |
96 | |
97 | /*! |
98 | * \property KPlotWidget::foregroundColor |
99 | */ |
100 | Q_PROPERTY(QColor foregroundColor READ foregroundColor WRITE setForegroundColor) |
101 | |
102 | /*! |
103 | * \property KPlotWidget::gridColor |
104 | */ |
105 | Q_PROPERTY(QColor gridColor READ gridColor WRITE setGridColor) |
106 | |
107 | /*! |
108 | * \property KPlotWidget::grid |
109 | */ |
110 | Q_PROPERTY(bool grid READ isGridShown WRITE setShowGrid) |
111 | |
112 | /*! |
113 | * \property KPlotWidget::objectToolTip |
114 | */ |
115 | Q_PROPERTY(bool objectToolTip READ isObjectToolTipShown WRITE setObjectToolTipShown) |
116 | public: |
117 | /*! |
118 | * Constructor. |
119 | * |
120 | * \a parent the parent widget |
121 | */ |
122 | explicit KPlotWidget(QWidget *parent = nullptr); |
123 | |
124 | ~KPlotWidget() override; |
125 | |
126 | /*! |
127 | * The four types of plot axes. |
128 | * |
129 | * \value LeftAxis the left axis |
130 | * \value BottomAxis the bottom axis |
131 | * \value RightAxis the right axis |
132 | * \value TopAxis the top axis |
133 | */ |
134 | enum Axis { |
135 | LeftAxis = 0, |
136 | BottomAxis, |
137 | RightAxis, |
138 | TopAxis, |
139 | }; |
140 | |
141 | /*! |
142 | * Returns suggested minimum size for the plot widget |
143 | */ |
144 | QSize minimumSizeHint() const override; |
145 | |
146 | /*! |
147 | * Returns suggested size for the plot widget |
148 | */ |
149 | QSize sizeHint() const override; |
150 | |
151 | /*! |
152 | * Set new data limits for the plot. |
153 | * |
154 | * \a x1 the minimum X value in data units |
155 | * |
156 | * \a x2 the maximum X value in data units |
157 | * |
158 | * \a y1 the minimum Y value in data units |
159 | * |
160 | * \a y2 the maximum Y value in data units |
161 | */ |
162 | void setLimits(double x1, double x2, double y1, double y2); |
163 | |
164 | /*! |
165 | * Reset the secondary data limits, which control the |
166 | * values displayed along the top and right axes. |
167 | * |
168 | * All data points are *plotted* using the coordinates |
169 | * defined by setLimits(), so this function is only useful for |
170 | * showing alternate tickmark labels along the top and right |
171 | * edges. For example, if you were plotting temperature on the |
172 | * X-axis, you could use Centigrade units for the primary |
173 | * (bottom) axis, using setLimits( 0.0, 100.0, 0.0, 1.0 ). If |
174 | * you also wanted to show Fahrenheit units along the secondary |
175 | * (top) axis, you would additionally use |
176 | * setSecondaryLimits( 32.0, 212.0, 0.0, 1.0 ). The data |
177 | * added to the plot would have x-coordinates in Centigrade degrees. |
178 | * |
179 | * \a x1 the minimum X value in secondary data units |
180 | * |
181 | * \a x2 the maximum X value in secondary data units |
182 | * |
183 | * \a y1 the minimum Y value in secondary data units |
184 | * |
185 | * \a y2 the maximum Y value in secondary data units |
186 | * |
187 | * \sa setLimits() |
188 | */ |
189 | void setSecondaryLimits(double x1, double x2, double y1, double y2); |
190 | |
191 | /*! |
192 | * Unset the secondary limits, so the top and right axes |
193 | * show the same tickmarks as the bottom and left axes (no tickmark |
194 | * labels will be drawn for the top and right axes in this case) |
195 | */ |
196 | void clearSecondaryLimits(); |
197 | |
198 | /*! |
199 | * Returns the rectangle representing the boundaries of the current plot, |
200 | * in natural data units. |
201 | * \sa setLimits() |
202 | */ |
203 | QRectF dataRect() const; |
204 | |
205 | /*! |
206 | * Returns the rectangle representing the boundaries of the secondary |
207 | * data limits, if they have been set. Otherwise, this function |
208 | * behaves the same as dataRect(). |
209 | * \sa setSecondaryLimits() |
210 | */ |
211 | QRectF secondaryDataRect() const; |
212 | |
213 | /*! |
214 | * Returns the rectangle representing the boundaries of the current plot, |
215 | * in screen pixel units. |
216 | */ |
217 | QRect pixRect() const; |
218 | |
219 | /*! |
220 | * Add an item to the list of KPlotObjects to be plotted. |
221 | * |
222 | * The widget takes ownership of the plot object, unless auto-delete was disabled. |
223 | * |
224 | * \a object the KPlotObject to be added |
225 | */ |
226 | void addPlotObject(KPlotObject *object); |
227 | |
228 | /*! |
229 | * Add more than one KPlotObject at one time. |
230 | * |
231 | * The widget takes ownership of the plot object, unless auto-delete was disabled. |
232 | * |
233 | * \a objects the list of KPlotObjects to be added |
234 | */ |
235 | void addPlotObjects(const QList<KPlotObject *> &objects); |
236 | |
237 | /*! |
238 | * Returns the current list of plot objects |
239 | */ |
240 | QList<KPlotObject *> plotObjects() const; |
241 | |
242 | /*! |
243 | * Enables auto-deletion of plot objects if autoDelete is true; otherwise auto-deletion is disabled. |
244 | * |
245 | * Auto-deletion is enabled by default. |
246 | * |
247 | * \since 5.12 |
248 | */ |
249 | void setAutoDeletePlotObjects(bool autoDelete); |
250 | |
251 | /*! |
252 | * Removes all plot objects that were added to the widget. |
253 | * |
254 | * If auto-delete was not disabled, the plot objects are deleted. |
255 | */ |
256 | void removeAllPlotObjects(); |
257 | |
258 | /*! |
259 | * Reset the mask used for non-overlapping labels so that all |
260 | * regions of the plot area are considered empty. |
261 | */ |
262 | void resetPlotMask(); |
263 | |
264 | /*! |
265 | * Clear the object list, reset the data limits, and remove axis labels |
266 | * |
267 | * If auto-delete was not disabled, the plot objects are deleted. |
268 | */ |
269 | void resetPlot(); |
270 | |
271 | /*! |
272 | * Replace an item in the KPlotObject list. |
273 | * |
274 | * \a i the index of the item to be replaced |
275 | * |
276 | * \a o pointer to the replacement KPlotObject |
277 | * |
278 | * Since 5.12, if auto-deletion is enabled, the previous plot object is deleted. |
279 | * Call setAutoDeletePlotObjects(false) if you want to swap between available plot objects |
280 | * and therefore you want to handle deletion externally. |
281 | */ |
282 | void replacePlotObject(int i, KPlotObject *o); |
283 | |
284 | /*! |
285 | * Returns the background color of the plot. |
286 | * |
287 | * The default color is black. |
288 | */ |
289 | QColor backgroundColor() const; |
290 | |
291 | /*! |
292 | * Returns the foreground color, used for axes, tickmarks and associated |
293 | * labels. |
294 | * |
295 | * The default color is white. |
296 | */ |
297 | QColor foregroundColor() const; |
298 | |
299 | /*! |
300 | * Returns the grid color. |
301 | * |
302 | * The default color is gray. |
303 | */ |
304 | QColor gridColor() const; |
305 | |
306 | /*! |
307 | * Set the background color |
308 | * |
309 | * \a bg the new background color |
310 | */ |
311 | void setBackgroundColor(const QColor &bg); |
312 | |
313 | /*! |
314 | * Set the foreground color |
315 | * |
316 | * \a fg the new foreground color |
317 | */ |
318 | void setForegroundColor(const QColor &fg); |
319 | |
320 | /*! |
321 | * Set the grid color |
322 | * |
323 | * \a gc the new grid color |
324 | */ |
325 | void setGridColor(const QColor &gc); |
326 | |
327 | /*! |
328 | * Returns whether the grid lines are shown |
329 | * |
330 | * Grid lines are not shown by default. |
331 | */ |
332 | bool isGridShown() const; |
333 | |
334 | /*! |
335 | * Returns whether the tooltip for the point objects is shown. |
336 | * |
337 | * Tooltips are enabled by default. |
338 | */ |
339 | bool isObjectToolTipShown() const; |
340 | |
341 | /*! |
342 | * Returns whether the antialiasing is active |
343 | * |
344 | * Antialiasing is not active by default. |
345 | */ |
346 | bool antialiasing() const; |
347 | |
348 | /*! |
349 | * Toggle antialiased drawing. |
350 | * |
351 | * \a b if true, the plot graphics will be antialiased. |
352 | */ |
353 | void setAntialiasing(bool b); |
354 | |
355 | /*! |
356 | * Returns the number of pixels to the left of the plot area. |
357 | * |
358 | * Padding values are set to -1 by default; if unchanged, this |
359 | * function will try to guess a good value, based on whether |
360 | * ticklabels and/or axis labels need to be drawn. |
361 | */ |
362 | int leftPadding() const; |
363 | |
364 | /*! |
365 | * Returns the number of pixels to the right of the plot area. |
366 | * |
367 | * Padding values are set to -1 by default; if unchanged, this |
368 | * function will try to guess a good value, based on whether |
369 | * ticklabels and/or axis labels are to be drawn. |
370 | */ |
371 | int rightPadding() const; |
372 | |
373 | /*! |
374 | * Returns the number of pixels above the plot area. |
375 | * |
376 | * Padding values are set to -1 by default; if unchanged, this |
377 | * function will try to guess a good value, based on whether |
378 | * ticklabels and/or axis labels are to be drawn. |
379 | */ |
380 | int topPadding() const; |
381 | |
382 | /*! |
383 | * Returns the number of pixels below the plot area. |
384 | * |
385 | * Padding values are set to -1 by default; if unchanged, this |
386 | * function will try to guess a good value, based on whether |
387 | * ticklabels and/or axis labels are to be drawn. |
388 | */ |
389 | int bottomPadding() const; |
390 | |
391 | /*! |
392 | * Set the number of pixels to the left of the plot area. |
393 | * |
394 | * Set this to -1 to revert to automatic determination of padding values. |
395 | */ |
396 | void setLeftPadding(int padding); |
397 | |
398 | /*! |
399 | * Set the number of pixels to the right of the plot area. |
400 | * |
401 | * Set this to -1 to revert to automatic determination of padding values. |
402 | */ |
403 | void setRightPadding(int padding); |
404 | |
405 | /*! |
406 | * Set the number of pixels above the plot area. |
407 | * |
408 | * Set this to -1 to revert to automatic determination of padding values. |
409 | */ |
410 | void setTopPadding(int padding); |
411 | |
412 | /*! |
413 | * Set the number of pixels below the plot area. |
414 | * |
415 | * Set this to -1 to revert to automatic determination of padding values. |
416 | */ |
417 | void setBottomPadding(int padding); |
418 | |
419 | /*! |
420 | * Revert all four padding values to -1, so that they will be |
421 | * automatically determined. |
422 | */ |
423 | void setDefaultPaddings(); |
424 | |
425 | /*! |
426 | * Map a coordinate \a p from the data rect to the physical |
427 | * pixel rect. |
428 | * |
429 | * Used mainly when drawing. |
430 | * |
431 | * \a p the point to be converted, in natural data units |
432 | * |
433 | * Returns the coordinate in the pixel coordinate system |
434 | */ |
435 | QPointF mapToWidget(const QPointF &p) const; |
436 | |
437 | /*! |
438 | * Indicate that object labels should try to avoid the given |
439 | * rectangle in the plot. The rectangle is in pixel coordinates. |
440 | * |
441 | * \note You should not normally call this function directly. |
442 | * It is called by KPlotObject when points, bars and labels are drawn. |
443 | * |
444 | * \a r the rectangle defining the region in the plot that |
445 | * text labels should avoid (in pixel coordinates) |
446 | * |
447 | * \a value Allows you to determine how strongly the rectangle |
448 | * should be avoided. Larger values are avoided more strongly. |
449 | */ |
450 | void maskRect(const QRectF &r, float value = 1.0f); |
451 | |
452 | /*! |
453 | * Indicate that object labels should try to avoid the line |
454 | * joining the two given points (in pixel coordinates). |
455 | * |
456 | * \note You should not normally call this function directly. |
457 | * It is called by KPlotObject when lines are drawn in the plot. |
458 | * |
459 | * \a p1 the starting point for the line |
460 | * |
461 | * \a p2 the ending point for the line |
462 | * |
463 | * \a value Allows you to determine how strongly the line |
464 | * should be avoided. Larger values are avoided more strongly. |
465 | */ |
466 | void maskAlongLine(const QPointF &p1, const QPointF &p2, float value = 1.0f); |
467 | |
468 | /*! |
469 | * Place an object label optimally in the plot. This function will |
470 | * attempt to place the label as close as it can to the point to which |
471 | * the label belongs, while avoiding overlap with regions of the plot |
472 | * that have been masked. |
473 | * |
474 | * \note You should not normally call this function directly. |
475 | * It is called internally in KPlotObject::draw(). |
476 | * |
477 | * \a painter Pointer to the painter on which to draw the label |
478 | * |
479 | * \a pp pointer to the KPlotPoint whose label is to be drawn. |
480 | */ |
481 | void placeLabel(QPainter *painter, KPlotPoint *pp); |
482 | |
483 | /*! |
484 | * Returns the axis of the specified \a type, or 0 if no axis has been set. |
485 | * \sa Axis |
486 | */ |
487 | KPlotAxis *axis(Axis type); |
488 | |
489 | /*! |
490 | * Returns the axis of the specified \a type, or 0 if no axis has been set. |
491 | * \sa Axis |
492 | */ |
493 | const KPlotAxis *axis(Axis type) const; |
494 | |
495 | public Q_SLOTS: |
496 | /*! |
497 | * Toggle whether grid lines are drawn at major tickmarks. |
498 | * |
499 | * \a show if true, grid lines will be drawn. |
500 | * |
501 | * \sa isGridShown() |
502 | */ |
503 | void setShowGrid(bool show); |
504 | |
505 | /*! |
506 | * Toggle the display of a tooltip for point objects. |
507 | * |
508 | * \a show whether show the tooltip. |
509 | * |
510 | * \sa isObjectToolTipShown() |
511 | */ |
512 | void setObjectToolTipShown(bool show); |
513 | |
514 | protected: |
515 | bool event(QEvent *) override; |
516 | |
517 | void paintEvent(QPaintEvent *) override; |
518 | |
519 | void resizeEvent(QResizeEvent *) override; |
520 | |
521 | /*! |
522 | * Draws the plot axes and axis labels. |
523 | * \internal |
524 | * Internal use only; one should simply call update() |
525 | * to draw the widget with axes and all objects. |
526 | * |
527 | * \a p pointer to the painter on which we are drawing |
528 | */ |
529 | virtual void drawAxes(QPainter *p); |
530 | |
531 | /*! |
532 | * Synchronize the PixRect with the current widget size and |
533 | * padding settings. |
534 | */ |
535 | void setPixRect(); |
536 | |
537 | /*! |
538 | * Returns a list of points in the plot which are within 4 pixels |
539 | * of the screen position given as an argument. |
540 | * |
541 | * \a p The screen position from which to check for plot points. |
542 | */ |
543 | QList<KPlotPoint *> pointsUnderPoint(const QPoint &p) const; |
544 | |
545 | private: |
546 | class Private; |
547 | std::unique_ptr<Private> const d; |
548 | |
549 | Q_DISABLE_COPY(KPlotWidget) |
550 | }; |
551 | |
552 | #endif |
553 | |