Updating zoom and axis across plots for consistent zoom levels and behavior.
[debian/gnuradio] / gr-qtgui / src / lib / FrequencyDisplayPlot.cc
1 #ifndef FREQUENCY_DISPLAY_PLOT_C
2 #define FREQUENCY_DISPLAY_PLOT_C
3
4 #include <FrequencyDisplayPlot.h>
5
6 #include <qwt_scale_draw.h>
7
8 class FreqPrecisionClass
9 {
10 public:
11   FreqPrecisionClass(const int freqPrecision)
12   {
13     _frequencyPrecision = freqPrecision;
14   }
15
16   virtual ~FreqPrecisionClass()
17   {
18   }
19
20   virtual unsigned int GetFrequencyPrecision() const
21   {
22     return _frequencyPrecision;
23   }
24
25   virtual void SetFrequencyPrecision(const unsigned int newPrecision)
26   {
27     _frequencyPrecision = newPrecision;
28   }
29 protected:
30   unsigned int _frequencyPrecision;
31
32 private:
33
34 };
35
36 class FreqDisplayScaleDraw: public QwtScaleDraw, public FreqPrecisionClass
37 {
38 public:
39   FreqDisplayScaleDraw(const unsigned int precision)
40     : QwtScaleDraw(), FreqPrecisionClass(precision)
41   {
42   }
43
44   virtual ~FreqDisplayScaleDraw() 
45   {
46   }
47
48   virtual QwtText label(double value) const
49   {
50     return QString("%1").arg(value, 0, 'f', GetFrequencyPrecision());
51   }
52
53 protected:
54
55 private:
56
57 };
58
59 class FreqDisplayZoomer: public QwtPlotZoomer, public FreqPrecisionClass
60 {
61 public:
62   FreqDisplayZoomer(QwtPlotCanvas* canvas, const unsigned int freqPrecision)
63     : QwtPlotZoomer(canvas),FreqPrecisionClass(freqPrecision)
64   {
65     setTrackerMode(QwtPicker::AlwaysOn);
66   }
67
68   virtual ~FreqDisplayZoomer(){
69
70   }
71   
72   virtual void updateTrackerText(){
73     updateDisplay();
74   }
75
76   void SetUnitType(const std::string &type)
77   {
78     _unitType = type;
79   }
80
81 protected:
82   virtual QwtText trackerText( const QwtDoublePoint& p ) const 
83   {
84     QwtText t(QString("%1 %2, %3 dB").
85               arg(p.x(), 0, 'f', GetFrequencyPrecision()).
86               arg(_unitType.c_str()).arg(p.y(), 0, 'f', 2));
87     return t;
88   }
89
90 private:
91   std::string _unitType;
92 };
93
94 FrequencyDisplayPlot::FrequencyDisplayPlot(QWidget* parent)
95   : QwtPlot(parent)
96 {
97   _startFrequency = 0;
98   _stopFrequency = 4000;
99   
100   timespec_reset(&_lastReplot);
101
102   resize(parent->width(), parent->height());
103   
104   _useCenterFrequencyFlag = false;
105
106   _numPoints = 1024;
107   _dataPoints = new double[_numPoints];
108   _minFFTPoints = new double[_numPoints];
109   _maxFFTPoints = new double[_numPoints];
110   _xAxisPoints = new double[_numPoints];
111
112   // Disable polygon clipping
113   QwtPainter::setDeviceClipping(false);
114   
115   // We don't need the cache here
116   canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, false);
117   canvas()->setPaintAttribute(QwtPlotCanvas::PaintPacked, false);
118   
119   QPalette palette;
120   palette.setColor(canvas()->backgroundRole(), QColor("white"));
121   canvas()->setPalette(palette);  
122
123   setAxisTitle(QwtPlot::xBottom, "Frequency (Hz)");
124   setAxisScaleDraw(QwtPlot::xBottom, new FreqDisplayScaleDraw(0));
125
126   _minYAxis = -120;
127   _maxYAxis = 10;
128   setAxisScaleEngine(QwtPlot::yLeft, new QwtLinearScaleEngine);
129   setAxisScale(QwtPlot::yLeft, _minYAxis, _maxYAxis);
130   setAxisTitle(QwtPlot::yLeft, "Power (dB)");
131
132   // Automatically deleted when parent is deleted
133   _fft_plot_curve = new QwtPlotCurve("Power Spectrum");
134   _fft_plot_curve->attach(this);
135   _fft_plot_curve->setPen(QPen(Qt::blue));
136   _fft_plot_curve->setRawData(_xAxisPoints, _dataPoints, _numPoints);
137
138   _min_fft_plot_curve = new QwtPlotCurve("Minimum Power");
139   _min_fft_plot_curve->attach(this);
140   _min_fft_plot_curve->setPen(QPen(Qt::magenta));
141   _min_fft_plot_curve->setRawData(_xAxisPoints, _minFFTPoints, _numPoints);
142   _min_fft_plot_curve->setVisible(false);
143
144   _max_fft_plot_curve = new QwtPlotCurve("Maximum Power");
145   _max_fft_plot_curve->attach(this);
146   _max_fft_plot_curve->setPen(QPen(Qt::darkYellow));
147   _max_fft_plot_curve->setRawData(_xAxisPoints, _maxFFTPoints, _numPoints);
148   _max_fft_plot_curve->setVisible(false);
149
150   _lower_intensity_marker = new QwtPlotMarker();
151   _lower_intensity_marker->setLineStyle(QwtPlotMarker::HLine);
152   _lower_intensity_marker->setLinePen(QPen(Qt::cyan));
153   _lower_intensity_marker->attach(this);
154
155   _upper_intensity_marker = new QwtPlotMarker();
156   _upper_intensity_marker->setLineStyle(QwtPlotMarker::HLine);
157   _upper_intensity_marker->setLinePen(QPen(Qt::green, 0, Qt::DotLine));
158   _upper_intensity_marker->attach(this);
159
160   memset(_dataPoints, 0x0, _numPoints*sizeof(double));
161   memset(_xAxisPoints, 0x0, _numPoints*sizeof(double));
162
163   for(int64_t number = 0; number < _numPoints; number++){
164     _minFFTPoints[number] = 200.0;
165     _maxFFTPoints[number] = -280.0;
166   }
167
168   // set up peak marker
169   QwtSymbol symbol;
170
171   _markerPeakAmplitude = new QwtPlotMarker();
172   _markerPeakAmplitude->setLinePen(QPen(Qt::yellow));
173   symbol.setStyle(QwtSymbol::Diamond);
174   symbol.setSize(8);
175   symbol.setPen(QPen(Qt::yellow));
176   symbol.setBrush(QBrush(Qt::yellow));
177   _markerPeakAmplitude->setSymbol(symbol);
178   _markerPeakAmplitude->attach(this);
179
180   _markerNoiseFloorAmplitude = new QwtPlotMarker();
181   _markerNoiseFloorAmplitude->setLineStyle(QwtPlotMarker::HLine);
182   _markerNoiseFloorAmplitude->setLinePen(QPen(Qt::darkRed, 0, Qt::DotLine));
183   _markerNoiseFloorAmplitude->attach(this);
184
185   _peakFrequency = 0;
186   _peakAmplitude = -HUGE_VAL;
187
188   _noiseFloorAmplitude = -HUGE_VAL;
189
190   replot();
191
192   _zoomer = new FreqDisplayZoomer(canvas(), 0);
193 #if QT_VERSION < 0x040000
194   _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
195                           Qt::RightButton, Qt::ControlModifier);
196 #else
197   _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
198                           Qt::RightButton, Qt::ControlModifier);
199 #endif
200   _zoomer->setMousePattern(QwtEventPattern::MouseSelect3,
201                           Qt::RightButton);
202
203   _panner = new QwtPlotPanner(canvas());
204   _panner->setAxisEnabled(QwtPlot::yRight, false);
205   _panner->setMouseButton(Qt::MidButton);
206
207   // Avoid jumping when labels with more/less digits
208   // appear/disappear when scrolling vertically
209
210   const QFontMetrics fm(axisWidget(QwtPlot::yLeft)->font());
211   QwtScaleDraw *sd = axisScaleDraw(QwtPlot::yLeft);
212   sd->setMinimumExtent( fm.width("100.00") );
213
214   const QColor c(Qt::darkRed);
215   _zoomer->setRubberBandPen(c);
216   _zoomer->setTrackerPen(c);
217
218   // Do this after the zoomer has been built
219   _resetXAxisPoints();
220 }
221
222 FrequencyDisplayPlot::~FrequencyDisplayPlot()
223 {
224   delete[] _dataPoints;
225   delete[] _maxFFTPoints;
226   delete[] _minFFTPoints;
227   delete[] _xAxisPoints;
228
229   // _fft_plot_curves deleted when parent deleted
230   // _zoomer and _panner deleted when parent deleted
231 }
232
233 void
234 FrequencyDisplayPlot::set_yaxis(double min, double max)
235 {
236   // Get the new max/min values for the plot
237   _minYAxis = min;
238   _maxYAxis = max;
239
240   // Set the axis max/min to the new values
241   setAxisScale(QwtPlot::yLeft, _minYAxis, _maxYAxis);
242
243   // Reset the base zoom level to the new axis scale set here
244   _zoomer->setZoomBase();
245 }
246
247 void
248 FrequencyDisplayPlot::SetFrequencyRange(const double constStartFreq,
249                                         const double constStopFreq,
250                                         const double constCenterFreq,
251                                         const bool useCenterFrequencyFlag,
252                                         const double units, const std::string &strunits)
253 {
254   double startFreq = constStartFreq / units;
255   double stopFreq = constStopFreq / units;
256   double centerFreq = constCenterFreq / units;
257
258   _useCenterFrequencyFlag = useCenterFrequencyFlag;
259
260   if(_useCenterFrequencyFlag){
261     startFreq = (startFreq + centerFreq);
262     stopFreq = (stopFreq + centerFreq);
263   }
264
265   bool reset = false;
266   if((startFreq != _startFrequency) || (stopFreq != _stopFrequency))
267     reset = true;
268
269   if(stopFreq > startFreq) {
270     _startFrequency = startFreq;
271     _stopFrequency = stopFreq;
272     
273     if((axisScaleDraw(QwtPlot::xBottom) != NULL) && (_zoomer != NULL)){
274       double display_units = ceil(log10(units)/2.0);
275       setAxisScaleDraw(QwtPlot::xBottom, new FreqDisplayScaleDraw(display_units));
276       setAxisTitle(QwtPlot::xBottom, QString("Frequency (%1)").arg(strunits.c_str()));
277
278       if(reset)
279         _resetXAxisPoints();
280       
281       ((FreqDisplayZoomer*)_zoomer)->SetFrequencyPrecision(display_units);
282       ((FreqDisplayZoomer*)_zoomer)->SetUnitType(strunits);
283     }
284   }
285 }
286
287
288 double
289 FrequencyDisplayPlot::GetStartFrequency() const
290 {
291   return _startFrequency;
292 }
293
294 double
295 FrequencyDisplayPlot::GetStopFrequency() const
296 {
297   return _stopFrequency;
298 }
299
300 void
301 FrequencyDisplayPlot::replot()
302 {
303   _markerNoiseFloorAmplitude->setYValue(_noiseFloorAmplitude);
304   
305   // Make sure to take into account the start frequency
306   if(_useCenterFrequencyFlag){
307     _markerPeakAmplitude->setXValue((_peakFrequency/1000.0) + _startFrequency);
308   }
309   else{
310     _markerPeakAmplitude->setXValue(_peakFrequency + _startFrequency);
311   }
312   _markerPeakAmplitude->setYValue(_peakAmplitude);
313   
314   QwtPlot::replot();
315 }
316  
317 void
318 FrequencyDisplayPlot::resizeSlot( QSize *s )
319 {
320   resize(s->width(), s->height());
321 }
322
323 void
324 FrequencyDisplayPlot::PlotNewData(const double* dataPoints, const int64_t numDataPoints,
325                                   const double noiseFloorAmplitude, const double peakFrequency,
326                                   const double peakAmplitude, const double timeInterval)
327 {
328   // Only update plot if there is data and if the time interval has elapsed
329   if((numDataPoints > 0) && 
330      (diff_timespec(get_highres_clock(), _lastReplot) > timeInterval)) {
331     
332     if(numDataPoints != _numPoints) {
333       _numPoints = numDataPoints;
334       
335       delete[] _dataPoints;
336       delete[] _minFFTPoints;
337       delete[] _maxFFTPoints;
338       delete[] _xAxisPoints;
339       _dataPoints = new double[_numPoints];
340       _xAxisPoints = new double[_numPoints];
341       _minFFTPoints = new double[_numPoints];
342       _maxFFTPoints = new double[_numPoints];
343       
344       _fft_plot_curve->setRawData(_xAxisPoints, _dataPoints, _numPoints);
345       _min_fft_plot_curve->setRawData(_xAxisPoints, _minFFTPoints, _numPoints);
346       _max_fft_plot_curve->setRawData(_xAxisPoints, _maxFFTPoints, _numPoints);
347       
348       _resetXAxisPoints();
349       ClearMaxData();
350       ClearMinData();
351     }
352     
353     memcpy(_dataPoints, dataPoints, numDataPoints*sizeof(double));
354     for(int64_t point = 0; point < numDataPoints; point++){
355       if(dataPoints[point] < _minFFTPoints[point]){
356         _minFFTPoints[point] = dataPoints[point];
357       }
358       if(dataPoints[point] > _maxFFTPoints[point]){
359         _maxFFTPoints[point] = dataPoints[point];
360       }
361     }
362
363     _noiseFloorAmplitude = noiseFloorAmplitude;
364     _peakFrequency = peakFrequency;
365     _peakAmplitude = peakAmplitude;
366
367     SetUpperIntensityLevel(_peakAmplitude);
368
369     replot();
370     
371     _lastReplot = get_highres_clock();
372   }
373 }
374
375 void
376 FrequencyDisplayPlot::ClearMaxData()
377 {
378   for(int64_t number = 0; number < _numPoints; number++){
379     _maxFFTPoints[number] = _minYAxis;
380   }
381 }
382
383 void
384 FrequencyDisplayPlot::ClearMinData()
385 {
386   for(int64_t number = 0; number < _numPoints; number++){
387     _minFFTPoints[number] = _maxYAxis;
388   }
389 }
390
391 void
392 FrequencyDisplayPlot::SetMaxFFTVisible(const bool visibleFlag)
393 {
394   _max_fft_plot_curve->setVisible(visibleFlag);
395 }
396
397 void
398 FrequencyDisplayPlot::SetMinFFTVisible(const bool visibleFlag)
399 {
400   _min_fft_plot_curve->setVisible(visibleFlag);
401 }
402
403 void
404 FrequencyDisplayPlot::_resetXAxisPoints()
405 {
406   double fft_bin_size = (_stopFrequency-_startFrequency) / static_cast<double>(_numPoints);
407   double freqValue = _startFrequency;
408   for(int64_t loc = 0; loc < _numPoints; loc++){
409     _xAxisPoints[loc] = freqValue;
410     freqValue += fft_bin_size;
411   }
412
413   setAxisScale(QwtPlot::xBottom, _startFrequency, _stopFrequency);
414
415   // Set up zoomer base for maximum unzoom x-axis
416   // and reset to maximum unzoom level
417   QwtDoubleRect zbase = _zoomer->zoomBase();
418   zbase.setLeft(_startFrequency);
419   zbase.setRight(_stopFrequency);
420   _zoomer->zoom(zbase);
421   _zoomer->setZoomBase(zbase);
422   _zoomer->zoom(0);
423 }
424
425 void
426 FrequencyDisplayPlot::SetLowerIntensityLevel(const double lowerIntensityLevel)
427 {
428   _lower_intensity_marker->setYValue( lowerIntensityLevel );
429 }
430
431 void
432 FrequencyDisplayPlot::SetUpperIntensityLevel(const double upperIntensityLevel)
433 {
434   _upper_intensity_marker->setYValue( upperIntensityLevel );
435 }
436
437
438 #endif /* FREQUENCY_DISPLAY_PLOT_C */