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