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):QwtPlot(parent){
188 _stopFrequency = 4000;
190 resize(parent->width(), parent->height());
193 _displayIntervalTime = (1.0/5.0); // 1/5 of a second between updates
195 _waterfallData = new WaterfallData(_startFrequency, _stopFrequency, _numPoints, 200);
198 palette.setColor(canvas()->backgroundRole(), QColor("white"));
199 canvas()->setPalette(palette);
201 setAxisTitle(QwtPlot::xBottom, "Frequency (Hz)");
202 setAxisScaleDraw(QwtPlot::xBottom, new WaterfallFreqDisplayScaleDraw(0));
204 setAxisTitle(QwtPlot::yLeft, "Time");
205 setAxisScaleDraw(QwtPlot::yLeft, new QwtTimeScaleDraw());
207 timespec_reset(&_lastReplot);
209 d_spectrogram = new PlotWaterfall(_waterfallData, "Waterfall Display");
211 _intensityColorMapType = INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR;
213 QwtLinearColorMap colorMap(Qt::darkCyan, Qt::white);
214 colorMap.addColorStop(0.25, Qt::cyan);
215 colorMap.addColorStop(0.5, Qt::yellow);
216 colorMap.addColorStop(0.75, Qt::red);
218 d_spectrogram->setColorMap(colorMap);
220 d_spectrogram->attach(this);
222 // LeftButton for the zooming
223 // MidButton for the panning
224 // RightButton: zoom out by 1
225 // Ctrl+RighButton: zoom out to full size
227 _zoomer = new WaterfallZoomer(canvas(), 0);
228 #if QT_VERSION < 0x040000
229 _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
230 Qt::RightButton, Qt::ControlModifier);
232 _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
233 Qt::RightButton, Qt::ControlModifier);
235 _zoomer->setMousePattern(QwtEventPattern::MouseSelect3,
238 _panner = new QwtPlotPanner(canvas());
239 _panner->setAxisEnabled(QwtPlot::yRight, false);
240 _panner->setMouseButton(Qt::MidButton);
242 // Avoid jumping when labels with more/less digits
243 // appear/disappear when scrolling vertically
245 const QFontMetrics fm(axisWidget(QwtPlot::yLeft)->font());
246 QwtScaleDraw *sd = axisScaleDraw(QwtPlot::yLeft);
247 sd->setMinimumExtent( fm.width("100.00") );
249 const QColor c(Qt::white);
250 _zoomer->setRubberBandPen(c);
251 _zoomer->setTrackerPen(c);
253 _UpdateIntensityRangeDisplay();
256 WaterfallDisplayPlot::~WaterfallDisplayPlot(){
257 delete _waterfallData;
260 void WaterfallDisplayPlot::Reset(){
261 _waterfallData->ResizeData(_startFrequency, _stopFrequency, _numPoints);
262 _waterfallData->Reset();
264 // Load up the new base zoom settings
265 QwtDoubleRect newSize = _zoomer->zoomBase();
266 newSize.setLeft(_startFrequency);
267 newSize.setWidth(_stopFrequency-_startFrequency);
268 _zoomer->zoom(newSize);
269 _zoomer->setZoomBase(newSize);
273 void WaterfallDisplayPlot::SetFrequencyRange(const double startFreq, const double stopFreq, const double centerFreq, const bool useCenterFrequencyFlag){
274 if((stopFreq > 0) && (stopFreq > startFreq)){
275 _startFrequency = startFreq;
276 _stopFrequency = stopFreq;
278 if((axisScaleDraw(QwtPlot::xBottom) != NULL) && (_zoomer != NULL)){
279 WaterfallFreqDisplayScaleDraw* freqScale = ((WaterfallFreqDisplayScaleDraw*)axisScaleDraw(QwtPlot::xBottom));
280 freqScale->SetCenterFrequency(centerFreq);
281 ((WaterfallZoomer*)_zoomer)->SetCenterFrequency(centerFreq);
282 if(useCenterFrequencyFlag){
283 freqScale->SetFrequencyPrecision( 3 );
284 ((WaterfallZoomer*)_zoomer)->SetFrequencyPrecision( 3 );
285 setAxisTitle(QwtPlot::xBottom, "Frequency (kHz)");
288 freqScale->SetFrequencyPrecision( 0 );
289 ((WaterfallZoomer*)_zoomer)->SetFrequencyPrecision( 0 );
290 setAxisTitle(QwtPlot::xBottom, "Frequency (Hz)");
296 // Only replot if screen is visible
304 double WaterfallDisplayPlot::GetStartFrequency()const{
305 return _startFrequency;
308 double WaterfallDisplayPlot::GetStopFrequency()const{
309 return _stopFrequency;
312 void WaterfallDisplayPlot::PlotNewData(const double* dataPoints, const int64_t numDataPoints, const double timePerFFT, const timespec timestamp, const int droppedFrames){
313 if(numDataPoints > 0){
314 if(numDataPoints != _numPoints){
315 _numPoints = numDataPoints;
319 d_spectrogram->invalidateCache();
320 d_spectrogram->itemChanged();
326 _lastReplot = get_highres_clock();
329 _waterfallData->addFFTData(dataPoints, numDataPoints, droppedFrames);
330 _waterfallData->IncrementNumLinesToUpdate();
332 QwtTimeScaleDraw* timeScale = (QwtTimeScaleDraw*)axisScaleDraw(QwtPlot::yLeft);
333 timeScale->SetSecondsPerLine(timePerFFT);
334 timeScale->SetZeroTime(timestamp);
336 ((WaterfallZoomer*)_zoomer)->SetSecondsPerLine(timePerFFT);
337 ((WaterfallZoomer*)_zoomer)->SetZeroTime(timestamp);
340 // Allow at least a 50% duty cycle
341 if(diff_timespec(get_highres_clock(), _lastReplot) > _displayIntervalTime){
343 d_spectrogram->invalidateCache();
344 d_spectrogram->itemChanged();
346 // Only update when window is visible
351 _lastReplot = get_highres_clock();
355 void WaterfallDisplayPlot::SetIntensityRange(const double minIntensity, const double maxIntensity){
356 _waterfallData->setRange(QwtDoubleInterval(minIntensity, maxIntensity));
358 emit UpdatedLowerIntensityLevel(minIntensity);
359 emit UpdatedUpperIntensityLevel(maxIntensity);
361 _UpdateIntensityRangeDisplay();
364 void WaterfallDisplayPlot::replot(){
365 const timespec startTime = get_highres_clock();
367 QwtTimeScaleDraw* timeScale = (QwtTimeScaleDraw*)axisScaleDraw(QwtPlot::yLeft);
368 timeScale->initiateUpdate();
370 WaterfallFreqDisplayScaleDraw* freqScale = (WaterfallFreqDisplayScaleDraw*)axisScaleDraw(QwtPlot::xBottom);
371 freqScale->initiateUpdate();
373 // Update the time axis display
374 if(axisWidget(QwtPlot::yLeft) != NULL){
375 axisWidget(QwtPlot::yLeft)->update();
378 // Update the Frequency Offset Display
379 if(axisWidget(QwtPlot::xBottom) != NULL){
380 axisWidget(QwtPlot::xBottom)->update();
384 ((WaterfallZoomer*)_zoomer)->updateTrackerText();
389 double differenceTime = (diff_timespec(get_highres_clock(), startTime));
391 // Require at least a 5% duty cycle
392 differenceTime *= 19.0;
393 if(differenceTime > (1.0/5.0)){
394 _displayIntervalTime = differenceTime;
398 int WaterfallDisplayPlot::GetIntensityColorMapType()const{
399 return _intensityColorMapType;
402 void WaterfallDisplayPlot::SetIntensityColorMapType(const int newType, const QColor lowColor, const QColor highColor){
403 if((_intensityColorMapType != newType) ||
404 ((newType == INTENSITY_COLOR_MAP_TYPE_USER_DEFINED) &&
405 (lowColor.isValid() && highColor.isValid()))){
407 case INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR:{
408 _intensityColorMapType = newType;
409 QwtLinearColorMap colorMap(Qt::darkCyan, Qt::white);
410 colorMap.addColorStop(0.25, Qt::cyan);
411 colorMap.addColorStop(0.5, Qt::yellow);
412 colorMap.addColorStop(0.75, Qt::red);
413 d_spectrogram->setColorMap(colorMap);
416 case INTENSITY_COLOR_MAP_TYPE_WHITE_HOT:{
417 _intensityColorMapType = newType;
418 QwtLinearColorMap colorMap(Qt::black, Qt::white);
419 d_spectrogram->setColorMap(colorMap);
422 case INTENSITY_COLOR_MAP_TYPE_BLACK_HOT:{
423 _intensityColorMapType = newType;
424 QwtLinearColorMap colorMap(Qt::white, Qt::black);
425 d_spectrogram->setColorMap(colorMap);
428 case INTENSITY_COLOR_MAP_TYPE_INCANDESCENT:{
429 _intensityColorMapType = newType;
430 QwtLinearColorMap colorMap(Qt::black, Qt::white);
431 colorMap.addColorStop(0.5, Qt::darkRed);
432 d_spectrogram->setColorMap(colorMap);
435 case INTENSITY_COLOR_MAP_TYPE_USER_DEFINED:{
436 _userDefinedLowIntensityColor = lowColor;
437 _userDefinedHighIntensityColor = highColor;
438 _intensityColorMapType = newType;
439 QwtLinearColorMap colorMap(_userDefinedLowIntensityColor, _userDefinedHighIntensityColor);
440 d_spectrogram->setColorMap(colorMap);
446 _UpdateIntensityRangeDisplay();
450 const QColor WaterfallDisplayPlot::GetUserDefinedLowIntensityColor()const{
451 return _userDefinedLowIntensityColor;
454 const QColor WaterfallDisplayPlot::GetUserDefinedHighIntensityColor()const{
455 return _userDefinedHighIntensityColor;
458 void WaterfallDisplayPlot::_UpdateIntensityRangeDisplay(){
459 QwtScaleWidget *rightAxis = axisWidget(QwtPlot::yRight);
460 rightAxis->setTitle("Intensity (dB)");
461 rightAxis->setColorBarEnabled(true);
462 rightAxis->setColorMap(d_spectrogram->data()->range(),
463 d_spectrogram->colorMap());
465 setAxisScale(QwtPlot::yRight,
466 d_spectrogram->data()->range().minValue(),
467 d_spectrogram->data()->range().maxValue() );
468 enableAxis(QwtPlot::yRight);
470 plotLayout()->setAlignCanvasToScales(true);
472 // Tell the display to redraw everything
473 d_spectrogram->invalidateCache();
474 d_spectrogram->itemChanged();
479 // Update the last replot timer
480 _lastReplot = get_highres_clock();
483 #endif /* WATERFALL_DISPLAY_PLOT_C */