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 */