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