File indexing completed on 2024-05-19 16:37:06
0001 /* 0002 SPDX-FileCopyrightText: 2006-2009 John Tapsell <tapsell@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #ifndef KGRAPHICSSIGNALPLOTTER_H 0008 #define KGRAPHICSSIGNALPLOTTER_H 0009 0010 #include <QColor> 0011 #include <QFont> 0012 #include <QGraphicsWidget> 0013 #include <QList> 0014 #include <QString> 0015 #include <klocalizedstring.h> 0016 0017 class QGraphicsSceneResizeEvent; 0018 class KGraphicsSignalPlotterPrivate; 0019 0020 // Make sure only declare KLocalizedString once 0021 #ifndef KSIGNALPLOTTER_H 0022 Q_DECLARE_METATYPE(KLocalizedString) 0023 #endif 0024 0025 /** \class KGraphicsSignalPlotter 0026 * \brief The KGraphicsSignalPlotter graphics widget draws a real time graph of data that updates continually. 0027 * 0028 * Features include: 0029 * \li Points are joined by a bezier curve. 0030 * \li Lines are anti-aliased 0031 * \li Background can be set as a specified SVG 0032 * \li The lines can be reordered 0033 * \li Uses as little memory and CPU as possible 0034 * \li Graph can be smoothed using the formula (value * 2 + last_value)/3 0035 * \li Can cope with positive/negative infinity and NaN values. 0036 * 0037 * Example usage: 0038 * \code 0039 * KGraphicsSignalPlotter *s = KGraphicsSignalPlotter(parent); 0040 * s->addBeam(Qt::blue); 0041 * s->addBeam(Qt::green); 0042 * QList<qreal> data; 0043 * data << 4.0 << 5.0; 0044 * s->addSample(data); 0045 * \endcode 0046 * 0047 * Note that the number of horizontal lines is calculated automatically based on the axis font size, even if the axis labels are not shown. 0048 * 0049 * Smoothing looks very nice visually and is enabled by default. It can be disabled with setSmoothGraph(). 0050 * 0051 * \image KSignalPlotter.png Example KGraphicsSignalPlotter with two beams 0052 */ 0053 class Q_DECL_EXPORT KGraphicsSignalPlotter : public QGraphicsWidget 0054 { 0055 Q_OBJECT 0056 Q_PROPERTY(qreal minimumValue READ minimumValue WRITE setMinimumValue) 0057 Q_PROPERTY(qreal maximumValue READ maximumValue WRITE setMaximumValue) 0058 Q_PROPERTY(bool useAutoRange READ useAutoRange WRITE setUseAutoRange) 0059 Q_PROPERTY(KLocalizedString unit READ unit WRITE setUnit) 0060 Q_PROPERTY(qreal scaleDownBy READ scaleDownBy WRITE setScaleDownBy) 0061 Q_PROPERTY(uint horizontalScale READ horizontalScale WRITE setHorizontalScale) 0062 Q_PROPERTY(bool showHorizontalLines READ showHorizontalLines WRITE setShowHorizontalLines) 0063 Q_PROPERTY(bool showVerticalLines READ showVerticalLines WRITE setShowVerticalLines) 0064 Q_PROPERTY(bool verticalLinesScroll READ verticalLinesScroll WRITE setVerticalLinesScroll) 0065 Q_PROPERTY(uint verticalLinesDistance READ verticalLinesDistance WRITE setVerticalLinesDistance) 0066 Q_PROPERTY(bool showAxis READ showAxis WRITE setShowAxis) 0067 Q_PROPERTY(QString svgBackground READ svgBackground WRITE setSvgBackground) 0068 Q_PROPERTY(bool thinFrame READ thinFrame WRITE setThinFrame) 0069 Q_PROPERTY(int maxAxisTextWidth READ maxAxisTextWidth WRITE setMaxAxisTextWidth) 0070 Q_PROPERTY(bool smoothGraph READ smoothGraph WRITE setSmoothGraph) 0071 Q_PROPERTY(bool stackGraph READ stackGraph WRITE setStackGraph) 0072 Q_PROPERTY(int fillOpacity READ fillOpacity WRITE setFillOpacity) 0073 0074 public: 0075 KGraphicsSignalPlotter(QGraphicsItem *parent = nullptr); 0076 ~KGraphicsSignalPlotter() override; 0077 0078 /** \brief Add a new line to the graph plotter, with the specified color. 0079 * 0080 * Note that the order you add the beams in must be the same order that 0081 * the beam data is given in (Unless you reorder the beams). 0082 * 0083 * \param color Color of beam - does not have to be unique. 0084 */ 0085 void addBeam(const QColor &color); 0086 0087 /** \brief Add data to the graph, and advance the graph by one time period. 0088 * 0089 * The data must be given as a list in the same order that the beams were 0090 * added (or consequently reordered). If samples.count() != numBeams(), 0091 * a warning is printed and the data discarded. 0092 * 0093 * To indicate that no data is available for a given beam, set its value to 0094 * (non-signalling) NaN. 0095 * 0096 * For example: 0097 * 0098 * \code 0099 * KSignalPlotter *s = KSignalPlotter(parent); 0100 * s->addBeam(Qt::red); 0101 * s->addBeam(Qt::green); 0102 * s->addBeam(Qt::blue); 0103 * signalPlotter->addSample( QList<qreal>() << std::numeric_limits<qreal>::quiet_NaN() << 1.0/0 << 10.0 ); 0104 * \endcode 0105 * 0106 * This indicates that no data is available yet for red (so the beam will not be drawn for this section), 0107 * that's it positive infinity for green, and 10 for blue. 0108 * 0109 * Infinity is handled by drawing a straight line up to the top or bottom of the display, and does not affect the range. 0110 * For the above example, the displayed range would now be 0 to 10. 0111 */ 0112 void addSample(const QList<qreal> &samples); 0113 0114 /** \brief Reorder the beams into the order given. 0115 * 0116 * For example: 0117 * \code 0118 * KSignalPlotter *s = KSignalPlotter(parent); 0119 * s->addBeam(Qt::blue); 0120 * s->addBeam(Qt::green); 0121 * s->addBeam(Qt::red); 0122 * QList<int> neworder; 0123 * neworder << 2 << 0 << 1; 0124 * s->reorderBeams( newOrder); 0125 * //Now the order is red, blue then green 0126 * \endcode 0127 * 0128 * The size of the \p newOrder list must be equal to the result of numBeams(). 0129 * \param newOrder New order of beams. 0130 */ 0131 void reorderBeams(const QList<int> &newOrder); 0132 0133 /** \brief Removes the beam at the specified index. 0134 * 0135 * This causes the graph to be redrawn with the specified beam completely 0136 * removed. 0137 */ 0138 void removeBeam(int index); 0139 0140 /** \brief Get the color of the beam at the specified index. 0141 * 0142 * For example: 0143 * \code 0144 * KSignalPlotter *s = KSignalPlotter(parent); 0145 * s->addBeam(Qt::blue); 0146 * s->addBeam(Qt::green); 0147 * s->addBeam(Qt::red); 0148 * 0149 * QColor color = s->beamColor(0); //returns blue 0150 * \endcode 0151 * 0152 * \sa setBeamColor() 0153 */ 0154 QColor beamColor(int index) const; 0155 0156 /** \brief Set the color of the beam at the specified index. 0157 * 0158 * \sa beamColor() 0159 */ 0160 void setBeamColor(int index, const QColor &color); 0161 0162 /** \brief Returns the number of beams. */ 0163 int numBeams() const; 0164 0165 /** \brief Set the axis units with a localized string. 0166 * 0167 * The localized string must contain a placeholder "%1" which is substituted for the value. 0168 * The plural form (ki18np) can be used if the unit string changes depending on the number (for example 0169 * "1 second", "2 seconds"). 0170 * 0171 * For example: 0172 * 0173 * \code 0174 * KSignalPlotter plotter; 0175 * plotter.setUnit( ki18ncp("Units", "%1 second", "%1 seconds") ); 0176 * QString formattedString = plotter.valueAsString(3.4); //returns "3.4 seconds" 0177 * \endcode 0178 * 0179 * Typically a new unit would be set when setScaleDownBy is called. 0180 * Note that even the singular should use "%1 second" instead of "1 second", so that a value of -1 works correctly. 0181 * 0182 * \see unit(), setScaleDownBy() 0183 */ 0184 void setUnit(const KLocalizedString &unit); 0185 0186 /** \brief The localizable units used on the vertical axis of the graph. 0187 * 0188 * The returns the localizable string set with setUnit(). 0189 * 0190 * Default is the string "%1" - i.e. to just display the number. 0191 * 0192 * \see setUnit 0193 */ 0194 KLocalizedString unit() const; 0195 0196 /** \brief Scale all the values down by the given amount. 0197 * 0198 * This is useful when the data is given in, say, kilobytes, but you set 0199 * the units as megabytes. Thus you would have to call this with @p value 0200 * set to 1024. This affects all the data already entered. 0201 * 0202 * Typically this is followed by calling setUnit() to set the display axis 0203 * units. Default value is 1. 0204 */ 0205 void setScaleDownBy(qreal value); 0206 0207 /** \brief Amount scaled down by. 0208 * 0209 * \sa setScaleDownBy */ 0210 qreal scaleDownBy() const; 0211 0212 /** \brief Set whether to scale the graph automatically beyond the given range. 0213 * 0214 * If true, the range on vertical axis is automatically expanded from the 0215 * data available, expanding beyond the range set by changeRange() if data 0216 * values are outside of this range. 0217 * 0218 * Regardless whether this is set of not, the range of the vertical axis 0219 * will never be less than the range given by maximumValue() and minimumvalue(). 0220 * 0221 * \param value Whether to scale beyond the given range. Default is true. 0222 * 0223 * \sa useAutoRange 0224 */ 0225 void setUseAutoRange(bool value); 0226 0227 /** \brief Whether the vertical axis range is set automatically. 0228 */ 0229 bool useAutoRange() const; 0230 0231 /** \brief Change the minimum and maximum values drawn on the graph. 0232 * 0233 * Note that these values are sanitised. For example, if you 0234 * set the minimum as 3, and the maximum as 97, then the graph 0235 * would be drawn between 0 and 100. The algorithm to determine 0236 * this "nice range" attempts to minimize the number of non-zero 0237 * digits. 0238 * 0239 * If autoRange() is true, then this range is taking as a 'hint'. 0240 * The range will never be smaller than the given range, but can grow 0241 * if there are values larger than the given range. 0242 * 0243 * This is equivalent to calling 0244 * \code 0245 * setMinimumValue(min); 0246 * setMaximumValue(max); 0247 * \endcode 0248 * 0249 * \sa setMinimumValue(), setMaximumValue(), minimumValue(), maximumValue() 0250 */ 0251 void changeRange(qreal min, qreal max); 0252 0253 /** \brief Set the min value hint for the vertical axis. 0254 * 0255 * \sa changeRange(), minimumValue(), setMaximumValue(), maximumValue() */ 0256 void setMinimumValue(qreal min); 0257 0258 /** \brief Get the min value hint for the vertical axis. 0259 * 0260 * \sa changeRange(), minimumValue(), setMaximumValue(), maximumValue() */ 0261 qreal minimumValue() const; 0262 0263 /** \brief Set the max value hint for the vertical axis. * 0264 * 0265 * \sa changeRange(), minimumValue(), setMaximumValue(), maximumValue() */ 0266 void setMaximumValue(qreal max); 0267 0268 /** \brief Get the maximum value hint for the vertical axis. 0269 * 0270 * \sa changeRange(), minimumValue(), setMaximumValue(), maximumValue() */ 0271 qreal maximumValue() const; 0272 0273 /** \brief Get the current maximum value on the y-axis. 0274 * 0275 * This will never be lower than maximumValue(), and if autoRange() is true, 0276 * it will be equal or larger (due to rounding up to make it a nice number) 0277 * than the highest value being shown. 0278 */ 0279 qreal currentMaximumRangeValue() const; 0280 /** \brief Get the current minimum value on the y-axis. 0281 * 0282 * This will never be lower than minimumValue(), and if autoRange() is true, 0283 * it will be equal or larger (due to rounding up to make it a nice number) 0284 * than the highest value being shown. 0285 */ 0286 qreal currentMinimumRangeValue() const; 0287 0288 /** \brief Set the number of pixels horizontally between data points. 0289 * Default is 6. */ 0290 void setHorizontalScale(uint scale); 0291 /** \brief The number of pixels horizontally between data points. 0292 * Default is 6. */ 0293 int horizontalScale() const; 0294 0295 /** \brief Set whether to draw the vertical grid lines. 0296 * Default is false. */ 0297 void setShowVerticalLines(bool value); 0298 /** \brief Whether to draw the vertical grid lines. 0299 * Default is false. */ 0300 bool showVerticalLines() const; 0301 0302 /** \brief Set the horizontal distance, in pixels, between the vertical grid lines. 0303 * Must be a distance of 1 or more. 0304 * Default is 30 pixels. */ 0305 void setVerticalLinesDistance(uint distance); 0306 /** \brief The horizontal distance, in pixels, between the vertical grid lines. 0307 * Default is 30 pixels. */ 0308 uint verticalLinesDistance() const; 0309 0310 /** \brief Set whether the vertical lines move with the data. 0311 * Default is true. This has no effect is showVerticalLines is false. */ 0312 void setVerticalLinesScroll(bool value); 0313 /** \brief Whether the vertical lines move with the data. 0314 * Default is true. This has no effect is showVerticalLines is false. */ 0315 bool verticalLinesScroll() const; 0316 0317 /** \brief Set whether to draw the horizontal grid lines. 0318 * Default is true. */ 0319 void setShowHorizontalLines(bool value); 0320 /** \brief Whether to draw the horizontal grid lines. 0321 * Default is true. */ 0322 bool showHorizontalLines() const; 0323 0324 /** \brief The number of decimal places used for the axis labels 0325 * 0326 * This is calculated based on the current range */ 0327 int currentAxisPrecision() const; 0328 0329 /** \brief Set whether to show the vertical axis labels. 0330 * 0331 * Default is true. 0332 * \sa showAxis(), setAxisFont(), setAxisFontColor(), setMaxAxisTextWidth() */ 0333 void setShowAxis(bool show); 0334 /** \brief Whether to show the vertical axis labels. 0335 * 0336 * Default is true. 0337 * \sa setShowAxis(), axisFont(), axisFontColor(), maxAxisTextWidth() */ 0338 bool showAxis() const; 0339 0340 /** \brief Set the filename of the SVG background. 0341 * 0342 * Set to empty (default) to disable again. */ 0343 void setSvgBackground(const QString &filename); 0344 0345 /** \brief The filename of the SVG background. */ 0346 QString svgBackground() const; 0347 0348 /** \brief Return the last value that we have for the given beam index. 0349 * 0350 * \return last value, or 0 if not known. */ 0351 qreal lastValue(int index) const; 0352 0353 /** \brief Return a translated string for the last value at the given index. 0354 * 0355 * Returns, for example, "34 %" or "100 KB" for the given beam index, 0356 * using the last value set for the beam, using the given precision. 0357 * 0358 * If precision is -1 (the default) then if @p value is greater than 99.5, no decimal figures are shown, 0359 * otherwise if @p value is greater than 0.995, 1 decimal figure is used, otherwise 2. 0360 */ 0361 QString lastValueAsString(int index, int precision = -1) const; 0362 0363 /** \brief Return a translated string for the given value. 0364 * 0365 * Returns, for example, "34 %" or "100 KB" for the given value in unscaled units. 0366 * 0367 * If precision is -1 (the default) then if @p value is greater than 99.5, no decimal figures are shown, 0368 * otherwise if @p value is greater than 0.995, 1 decimal figure is used, otherwise 2. 0369 * 0370 * For example: 0371 * \code 0372 * KSignalPlotter plotter; 0373 * plotter.setUnit( ki18ncp("Units", "1 hour", "%1 hours") ); 0374 * plotter.scaleDownBy( 60 ); //The input will be in seconds, and there's 60 seconds in an hour 0375 * QString formattedString = plotter.valueAsString(150); //returns "2.5 hours" 0376 * \endcode 0377 * 0378 */ 0379 QString valueAsString(qreal value, int precision = -1) const; 0380 0381 /** \brief Set the distance between the left of the widget and the left of the plotting region. 0382 * 0383 * For example: 0384 * \code 0385 * int axisTextWidth = fontMetrics().width(i18nc("Largest axis title", "99999 XXXX")); 0386 * plotter->setMaxAxisTextWidth(axisTextWidth); 0387 * \endcode 0388 * 0389 * If this is 0, the default, then the text will be shown inside the plotting area. 0390 */ 0391 void setMaxAxisTextWidth(int maxAxisTextWidth); 0392 /** \brief Get the distance between the left of the widget and the left of the plotting region. */ 0393 int maxAxisTextWidth() const; 0394 0395 /** \brief Set whether to smooth the graph by averaging the points. 0396 * 0397 * This uses the formula: (value*2 + last_value)/3. 0398 * Default is true. */ 0399 void setSmoothGraph(bool smooth); 0400 /** \brief Whether to smooth the graph by averaging the points. 0401 * 0402 * This uses the formula: (value*2 + last_value)/3. 0403 * Default is true. */ 0404 bool smoothGraph() const; 0405 0406 /** \brief Set whether to stack the beams on top of each other. 0407 * 0408 * Default is false */ 0409 void setStackGraph(bool stack); 0410 /** \brief Whether to stack the beams on top of each other. 0411 * 0412 * Default is false */ 0413 bool stackGraph() const; 0414 0415 /** \brief Alpha value for filling the area underneath the graph lines. 0416 * 0417 * Set to 0 to disable filling the graph, and 255 for a solid fill. Default is 20*/ 0418 void setFillOpacity(int fill); 0419 /** \brief Alpha value for filling the area underneath the graph lines. */ 0420 int fillOpacity() const; 0421 0422 /* Whether to show a thin line on the left and bottom of the widget, for a slight 3D effect */ 0423 bool thinFrame() const; 0424 /* Set whether to show a thin line on the left and bottom of the widget, for a slight 3D effect */ 0425 void setThinFrame(bool thinFrame); 0426 0427 Q_SIGNALS: 0428 /** When the axis has changed this signal is emitted. */ 0429 void axisScaleChanged(); 0430 0431 protected: 0432 /* Reimplemented */ 0433 void resizeEvent(QGraphicsSceneResizeEvent *event) override; 0434 void paint(QPainter *p, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; 0435 QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const override; 0436 void changeEvent(QEvent *event) override; 0437 QPainterPath opaqueArea() const override; 0438 0439 private: 0440 KGraphicsSignalPlotterPrivate *const d; 0441 friend class KGraphicsSignalPlotterPrivate; 0442 }; 0443 0444 #endif