Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# HTML Canvas/WebSockets Waterfall

This is a small experiment to create a waterfall plot with HTML Canvas and WebSockets to stream live FFT data from an SDR:

![alt](img/waterfall.png)

## Installation

```bash
$ npm install git+https://github.com/jledet/waterfall.git
```

## Usage

```ecmascript 6
import Spectrum from 'fft-waterfall';
```

## Files definition
`spectrum.js` contains the main JavaScript source code for the plot, while `colormap.js` contains colormaps generated using ``make_colormap.py``.

`index.html`, `style.css`, `script.js` contain an example page that receives FFT data on a WebSocket and plots it on the waterfall plot.

`server.py` contains a example [Bottle](https://bottlepy.org/docs/dev/) and [gevent-websocket](https://pypi.org/project/gevent-websocket/) server
that broadcasts FFT data to connected clients. The FFT data is generated using [GNU radio](https://www.gnuradio.org/) using a USRP but it
should be fairly easy to change it to a different SDR.
13 changes: 0 additions & 13 deletions README.rst

This file was deleted.

2 changes: 2 additions & 0 deletions colormap.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "fft-waterfall",
"version": "1.0.0",
"description": "This is a small experiment to create a waterfall plot with HTML Canvas and WebSockets to stream live FFT data from an SDR",
"main": "spectrum.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://github.com/jledet/waterfall"
},
"keywords": [
"waterfall",
"sdr",
"fft",
"canvas",
"plot",
"websocket"
],
"author": "Jeppe Ledet-Pedersen",
"license": "MIT"
}
33 changes: 25 additions & 8 deletions spectrum.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

'use strict';
var colormaps = require('./colormap.js');

Spectrum.prototype.squeeze = function(value, out_min, out_max) {
if (value <= this.min_db)
Expand Down Expand Up @@ -128,27 +129,36 @@ Spectrum.prototype.updateAxes = function() {
}

this.ctx_axes.textBaseline = "bottom";
for (var i = 0; i < 11; i++) {
var x = Math.round(width / 10) * i;
for (var i = 0; i < this.ticksHz; i++) {
var x = Math.round(width / (this.ticksHz - 1)) * i;

if (this.spanHz > 0) {
var adjust = 0;
if (i == 0) {
if (i === 0) {
this.ctx_axes.textAlign = "left";
adjust = 3;
} else if (i == 10) {
} else if (i === (this.ticksHz - 1)) {
this.ctx_axes.textAlign = "right";
adjust = -3;
} else {
this.ctx_axes.textAlign = "center";
}

var freq = this.centerHz + this.spanHz / 10 * (i - 5);
var freqFactor = (2 / (this.ticksHz - 1)) * i - 1; // range <-1; +1>
var freq = this.centerHz + this.spanHz * freqFactor;
if (this.centerHz + this.spanHz > 1e6)
freq = freq / 1e6 + "M";
freq = (freq / 1e6).toFixed(2) + "M";
else if (this.centerHz + this.spanHz > 1e3)
freq = freq / 1e3 + "k";
this.ctx_axes.fillText(freq, x + adjust, height - 3);
freq = (freq / 1e3).toFixed(2) + "k";

if(this.horizontalAxisPosition === 'both') {
this.ctx_axes.fillText(freq, x + adjust, height);
this.ctx_axes.fillText(freq, x + adjust, 12);
} else if(this.horizontalAxisPosition === 'bottom') {
this.ctx_axes.fillText(freq, x + adjust, height);
} else if(this.horizontalAxisPosition === 'top') {
this.ctx_axes.fillText(freq, x + adjust, 12);
}
}

this.ctx_axes.beginPath();
Expand Down Expand Up @@ -328,6 +338,8 @@ function Spectrum(id, options) {
this.spectrumPercent = (options && options.spectrumPercent) ? options.spectrumPercent : 25;
this.spectrumPercentStep = (options && options.spectrumPercentStep) ? options.spectrumPercentStep : 5;
this.averaging = (options && options.averaging) ? options.averaging : 0.5;
this.ticksHz = (options && options.ticksHz) ? options.ticksHz : 10;
this.horizontalAxisPosition = (options && options.horizontalAxisPosition) ? options.horizontalAxisPosition : 'bottom'; // either 'top', 'bottom' or 'both'

// Setup state
this.paused = false;
Expand All @@ -342,6 +354,9 @@ function Spectrum(id, options) {

// Create main canvas and adjust dimensions to match actual
this.canvas = document.getElementById(id);
if(this.canvas === null) {
throw "There is no <canvas> declared with id #" + id
}
this.canvas.height = this.canvas.clientHeight;
this.canvas.width = this.canvas.clientWidth;
this.ctx = this.canvas.getContext("2d");
Expand All @@ -364,3 +379,5 @@ function Spectrum(id, options) {
this.updateSpectrumRatio();
this.resize();
}

module.exports = Spectrum;