1 #ifndef WATERFALL_DISPLAY_PLOT_C
2 #define WATERFALL_DISPLAY_PLOT_C
4 #include <WaterfallDisplayPlot.h>
6 #include <qwt_color_map.h>
7 #include <qwt_scale_widget.h>
8 #include <qwt_scale_draw.h>
9 #include <qwt_plot_zoomer.h>
10 #include <qwt_plot_panner.h>
11 #include <qwt_plot_layout.h>
13 #include <qapplication.h>
15 class FreqOffsetAndPrecisionClass
18 FreqOffsetAndPrecisionClass(const int freqPrecision){
19 _frequencyPrecision = freqPrecision;
23 virtual ~FreqOffsetAndPrecisionClass(){
27 virtual unsigned int GetFrequencyPrecision()const{
28 return _frequencyPrecision;
31 virtual void SetFrequencyPrecision(const unsigned int newPrecision){
32 _frequencyPrecision = newPrecision;
35 virtual double GetCenterFrequency()const{
36 return _centerFrequency;
39 virtual void SetCenterFrequency(const double newFreq){
40 _centerFrequency = newFreq;
44 unsigned int _frequencyPrecision;
45 double _centerFrequency;
51 class WaterfallFreqDisplayScaleDraw: public QwtScaleDraw, public FreqOffsetAndPrecisionClass{
53 WaterfallFreqDisplayScaleDraw(const unsigned int precision):QwtScaleDraw(), FreqOffsetAndPrecisionClass(precision){
57 virtual ~WaterfallFreqDisplayScaleDraw(){
61 QwtText label(double value)const{
62 return QString("%1").arg((value + GetCenterFrequency()) / ((GetFrequencyPrecision() == 0) ? 1.0 : 1000.0), 0, 'f', GetFrequencyPrecision());
65 virtual void initiateUpdate(){
79 timespec_reset(&_zeroTime);
80 _secondsPerLine = 1.0;
84 virtual ~TimeScaleData(){
88 virtual timespec GetZeroTime()const{
92 virtual void SetZeroTime(const timespec newTime){
96 virtual void SetSecondsPerLine(const double newTime){
97 _secondsPerLine = newTime;
100 virtual double GetSecondsPerLine()const{
101 return _secondsPerLine;
107 double _secondsPerLine;
113 class QwtTimeScaleDraw: public QwtScaleDraw, public TimeScaleData
116 QwtTimeScaleDraw():QwtScaleDraw(),TimeScaleData(){
120 virtual ~QwtTimeScaleDraw(){
124 virtual QwtText label(double value)const{
125 QwtText returnLabel("");
127 timespec lineTime = timespec_add(GetZeroTime(), (-value) * GetSecondsPerLine());
129 gmtime_r(&lineTime.tv_sec, &timeTm);
130 returnLabel = (QString("").sprintf("%04d/%02d/%02d\n%02d:%02d:%02d.%03ld", timeTm.tm_year+1900, timeTm.tm_mon+1, timeTm.tm_mday, timeTm.tm_hour, timeTm.tm_min, timeTm.tm_sec, lineTime.tv_nsec/1000000));
135 virtual void initiateUpdate(){
136 // Do this in one call rather than when zeroTime and secondsPerLine updates is to prevent the display from being updated too often...
146 class WaterfallZoomer: public QwtPlotZoomer, public TimeScaleData, public FreqOffsetAndPrecisionClass
149 WaterfallZoomer(QwtPlotCanvas* canvas, const unsigned int freqPrecision):QwtPlotZoomer(canvas), TimeScaleData(), FreqOffsetAndPrecisionClass(freqPrecision)
151 setTrackerMode(QwtPicker::AlwaysOn);
154 virtual ~WaterfallZoomer(){
158 virtual void updateTrackerText(){
163 virtual QwtText trackerText( const QwtDoublePoint& p ) const
167 timespec lineTime = timespec_add(GetZeroTime(), (-p.y()) * GetSecondsPerLine());
169 gmtime_r(&lineTime.tv_sec, &timeTm);
170 yLabel = (QString("").sprintf("%04d/%02d/%02d %02d:%02d:%02d.%03ld", timeTm.tm_year+1900, timeTm.tm_mon+1, timeTm.tm_mday, timeTm.tm_hour, timeTm.tm_min, timeTm.tm_sec, lineTime.tv_nsec/1000000));
172 QwtText t(QString("%1 %2, %3").arg((p.x() + GetCenterFrequency()) / ((GetFrequencyPrecision() == 0) ? 1.0 : 1000.0), 0, 'f', GetFrequencyPrecision()).arg( (GetFrequencyPrecision() == 0) ? "Hz" : "kHz").arg(yLabel));
179 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR;
180 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_WHITE_HOT;
181 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_BLACK_HOT;
182 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_INCANDESCENT;
183 const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_USER_DEFINED;
185 WaterfallDisplayPlot::WaterfallDisplayPlot(QWidget* parent)
190 _stopFrequency = 4000;
192 resize(parent->width(), parent->height());
195 _displayIntervalTime = (1.0/5.0); // 1/5 of a second between updates
197 _waterfallData = new WaterfallData(_startFrequency, _stopFrequency, _numPoints, 200);
200 palette.setColor(canvas()->backgroundRole(), QColor("white"));
201 canvas()->setPalette(palette);
203 setAxisTitle(QwtPlot::xBottom, "Frequency (Hz)");
204 setAxisScaleDraw(QwtPlot::xBottom, new WaterfallFreqDisplayScaleDraw(0));
206 setAxisTitle(QwtPlot::yLeft, "Time");
207 setAxisScaleDraw(QwtPlot::yLeft, new QwtTimeScaleDraw());
209 timespec_reset(&_lastReplot);
211 d_spectrogram = new PlotWaterfall(_waterfallData, "Waterfall Display");
213 _intensityColorMapType = INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR;
215 QwtLinearColorMap colorMap(Qt::darkCyan, Qt::white);
216 colorMap.addColorStop(0.25, Qt::cyan);
217 colorMap.addColorStop(0.5, Qt::yellow);
218 colorMap.addColorStop(0.75, Qt::red);
220 d_spectrogram->setColorMap(colorMap);
222 d_spectrogram->attach(this);
224 // LeftButton for the zooming
225 // MidButton for the panning
226 // RightButton: zoom out by 1
227 // Ctrl+RighButton: zoom out to full size
229 _zoomer = new WaterfallZoomer(canvas(), 0);
230 #if QT_VERSION < 0x040000
231 _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
232 Qt::RightButton, Qt::ControlModifier);
234 _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
235 Qt::RightButton, Qt::ControlModifier);
237 _zoomer->setMousePattern(QwtEventPattern::MouseSelect3,
240 _panner = new QwtPlotPanner(canvas());
241 _panner->setAxisEnabled(QwtPlot::yRight, false);
242 _panner->setMouseButton(Qt::MidButton);
244 // Avoid jumping when labels with more/less digits
245 // appear/disappear when scrolling vertically
247 const QFontMetrics fm(axisWidget(QwtPlot::yLeft)->font());
248 QwtScaleDraw *sd = axisScaleDraw(QwtPlot::yLeft);
249 sd->setMinimumExtent( fm.width("100.00") );
251 const QColor c(Qt::white);
252 _zoomer->setRubberBandPen(c);
253 _zoomer->setTrackerPen(c);
255 _UpdateIntensityRangeDisplay();
258 WaterfallDisplayPlot::~WaterfallDisplayPlot()
260 delete _waterfallData;
264 WaterfallDisplayPlot::Reset()
266 _waterfallData->ResizeData(_startFrequency, _stopFrequency, _numPoints);
267 _waterfallData->Reset();
269 // Load up the new base zoom settings
270 QwtDoubleRect newSize = _zoomer->zoomBase();
271 newSize.setLeft(_startFrequency);
272 newSize.setWidth(_stopFrequency-_startFrequency);
273 _zoomer->zoom(newSize);
274 _zoomer->setZoomBase(newSize);
279 WaterfallDisplayPlot::SetFrequencyRange(const double constStartFreq,
280 const double constStopFreq,
281 const double constCenterFreq,
282 const bool useCenterFrequencyFlag,
283 const double units, const std::string &strunits)
285 double startFreq = constStartFreq / units;
286 double stopFreq = constStopFreq / units;
287 double centerFreq = constCenterFreq / units;
289 if(stopFreq > startFreq) {
290 _startFrequency = 1000*startFreq;
291 _stopFrequency = 1000*stopFreq;
293 setAxisScale(QwtPlot::xBottom, _startFrequency, _stopFrequency);
295 if((axisScaleDraw(QwtPlot::xBottom) != NULL) && (_zoomer != NULL)){
296 WaterfallFreqDisplayScaleDraw* freqScale = ((WaterfallFreqDisplayScaleDraw*)axisScaleDraw(QwtPlot::xBottom));
297 freqScale->SetCenterFrequency(centerFreq);
298 ((WaterfallZoomer*)_zoomer)->SetCenterFrequency(centerFreq);
300 freqScale->SetFrequencyPrecision( 2 );
301 ((WaterfallZoomer*)_zoomer)->SetFrequencyPrecision( 2 );
302 setAxisTitle(QwtPlot::xBottom, QString("Frequency (%1)").arg(strunits.c_str()));
307 // Only replot if screen is visible
315 double WaterfallDisplayPlot::GetStartFrequency()const{
316 return _startFrequency;
319 double WaterfallDisplayPlot::GetStopFrequency()const{
320 return _stopFrequency;
323 void WaterfallDisplayPlot::PlotNewData(const double* dataPoints, const int64_t numDataPoints, const double timePerFFT, const timespec timestamp, const int droppedFrames){
324 if(numDataPoints > 0){
325 if(numDataPoints != _numPoints){
326 _numPoints = numDataPoints;
330 d_spectrogram->invalidateCache();
331 d_spectrogram->itemChanged();
337 _lastReplot = get_highres_clock();
340 _waterfallData->addFFTData(dataPoints, numDataPoints, droppedFrames);
341 _waterfallData->IncrementNumLinesToUpdate();
343 QwtTimeScaleDraw* timeScale = (QwtTimeScaleDraw*)axisScaleDraw(QwtPlot::yLeft);
344 timeScale->SetSecondsPerLine(timePerFFT);
345 timeScale->SetZeroTime(timestamp);
347 ((WaterfallZoomer*)_zoomer)->SetSecondsPerLine(timePerFFT);
348 ((WaterfallZoomer*)_zoomer)->SetZeroTime(timestamp);
351 // Allow at least a 50% duty cycle
352 if(diff_timespec(get_highres_clock(), _lastReplot) > _displayIntervalTime){
354 d_spectrogram->invalidateCache();
355 d_spectrogram->itemChanged();
357 // Only update when window is visible
362 _lastReplot = get_highres_clock();
366 void WaterfallDisplayPlot::SetIntensityRange(const double minIntensity, const double maxIntensity){
367 _waterfallData->setRange(QwtDoubleInterval(minIntensity, maxIntensity));
369 emit UpdatedLowerIntensityLevel(minIntensity);
370 emit UpdatedUpperIntensityLevel(maxIntensity);
372 _UpdateIntensityRangeDisplay();
375 void WaterfallDisplayPlot::replot(){
376 const timespec startTime = get_highres_clock();
378 QwtTimeScaleDraw* timeScale = (QwtTimeScaleDraw*)axisScaleDraw(QwtPlot::yLeft);
379 timeScale->initiateUpdate();
381 WaterfallFreqDisplayScaleDraw* freqScale = (WaterfallFreqDisplayScaleDraw*)axisScaleDraw(QwtPlot::xBottom);
382 freqScale->initiateUpdate();
384 // Update the time axis display
385 if(axisWidget(QwtPlot::yLeft) != NULL){
386 axisWidget(QwtPlot::yLeft)->update();
389 // Update the Frequency Offset Display
390 if(axisWidget(QwtPlot::xBottom) != NULL){
391 axisWidget(QwtPlot::xBottom)->update();
395 ((WaterfallZoomer*)_zoomer)->updateTrackerText();
400 double differenceTime = (diff_timespec(get_highres_clock(), startTime));
402 // Require at least a 5% duty cycle
403 differenceTime *= 19.0;
404 if(differenceTime > (1.0/5.0)){
405 _displayIntervalTime = differenceTime;
409 int WaterfallDisplayPlot::GetIntensityColorMapType()const{
410 return _intensityColorMapType;
413 void WaterfallDisplayPlot::SetIntensityColorMapType(const int newType, const QColor lowColor, const QColor highColor){
414 if((_intensityColorMapType != newType) ||
415 ((newType == INTENSITY_COLOR_MAP_TYPE_USER_DEFINED) &&
416 (lowColor.isValid() && highColor.isValid()))){
418 case INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR:{
419 _intensityColorMapType = newType;
420 QwtLinearColorMap colorMap(Qt::darkCyan, Qt::white);
421 colorMap.addColorStop(0.25, Qt::cyan);
422 colorMap.addColorStop(0.5, Qt::yellow);
423 colorMap.addColorStop(0.75, Qt::red);
424 d_spectrogram->setColorMap(colorMap);
427 case INTENSITY_COLOR_MAP_TYPE_WHITE_HOT:{
428 _intensityColorMapType = newType;
429 QwtLinearColorMap colorMap(Qt::black, Qt::white);
430 d_spectrogram->setColorMap(colorMap);
433 case INTENSITY_COLOR_MAP_TYPE_BLACK_HOT:{
434 _intensityColorMapType = newType;
435 QwtLinearColorMap colorMap(Qt::white, Qt::black);
436 d_spectrogram->setColorMap(colorMap);
439 case INTENSITY_COLOR_MAP_TYPE_INCANDESCENT:{
440 _intensityColorMapType = newType;
441 QwtLinearColorMap colorMap(Qt::black, Qt::white);
442 colorMap.addColorStop(0.5, Qt::darkRed);
443 d_spectrogram->setColorMap(colorMap);
446 case INTENSITY_COLOR_MAP_TYPE_USER_DEFINED:{
447 _userDefinedLowIntensityColor = lowColor;
448 _userDefinedHighIntensityColor = highColor;
449 _intensityColorMapType = newType;
450 QwtLinearColorMap colorMap(_userDefinedLowIntensityColor, _userDefinedHighIntensityColor);
451 d_spectrogram->setColorMap(colorMap);
457 _UpdateIntensityRangeDisplay();
461 const QColor WaterfallDisplayPlot::GetUserDefinedLowIntensityColor()const{
462 return _userDefinedLowIntensityColor;
465 const QColor WaterfallDisplayPlot::GetUserDefinedHighIntensityColor()const{
466 return _userDefinedHighIntensityColor;
469 void WaterfallDisplayPlot::_UpdateIntensityRangeDisplay(){
470 QwtScaleWidget *rightAxis = axisWidget(QwtPlot::yRight);
471 rightAxis->setTitle("Intensity (dB)");
472 rightAxis->setColorBarEnabled(true);
473 rightAxis->setColorMap(d_spectrogram->data()->range(),
474 d_spectrogram->colorMap());
476 setAxisScale(QwtPlot::yRight,
477 d_spectrogram->data()->range().minValue(),
478 d_spectrogram->data()->range().maxValue() );
479 enableAxis(QwtPlot::yRight);
481 plotLayout()->setAlignCanvasToScales(true);
483 // Tell the display to redraw everything
484 d_spectrogram->invalidateCache();
485 d_spectrogram->itemChanged();
490 // Update the last replot timer
491 _lastReplot = get_highres_clock();
494 #endif /* WATERFALL_DISPLAY_PLOT_C */