2

I was trying to Build a spectrum and waterfall Plot HTML canvas. After a long research and googling i found this SOURCE CODE

Now i am trying to learn how this code works. i added 3 points on the spectrum using drawPoint2 function.

Can some one please Guild me. thank you.

"use strict";

var colors = [
  "rgba(60, 229, 42, 0.31)",
  "rgba(60, 229, 42, 0.31)",
  "rgba(60, 229, 42, 0.31)",
  "rgba(60, 229, 42, 0.31)",
  "rgba(60, 229, 42, 0.31)",
  "rgba(252, 182, 3, 0.31)",
  "rgba(3, 103, 252, 0.31)",
  "rgba(219, 3, 252, 0.31)",
  "rgba(252, 3, 49, 0.31)",
  "rgba(221, 48, 232, 0.31)",
];

var closeEnough = 5;
var crop = 150;
var data_sb = -10;
var canvas = document.getElementById("spectrumSM");

Spectrum.prototype.countDecimals = function (value) {
  if (Math.floor(value) !== value)
    return value.toString().split(".")[1].length || 0;
  return 0;
};

Spectrum.prototype.map = function (x, in_min, in_max, out_min, out_max) {
  return Math.round(
    ((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
  );
};

Spectrum.prototype.updateSpectrumRatio = function () {
  this.spectrumHeight = Math.round(
    (this.canvas.height * this.spectrumPercent) / 100.0
  );

  // create a slop
  this.gradient = this.ctx.createLinearGradient(0, 0, 0, this.spectrumHeight);
  for (var i = 0; i < this.colormap.length; i++) {
    var c = this.colormap[this.colormap.length - 1 - i];
    this.gradient.addColorStop(
      i / this.colormap.length,
      "rgba(220,220,220,0.2)"
    ); //hardcode the patch above Xaxis
  }
};

Spectrum.prototype.resize = function () {
  var width = this.canvas.clientWidth;
  var height = this.canvas.clientHeight;

  if (this.canvas.width != width || this.canvas.height != height) {
    this.canvas.width = width;
    this.canvas.height = height;
    this.updateSpectrumRatio();
    for (var z = 0; z < this.tags.length; z++) {
      this.tags[z].StayX = this.map(
        this.tags[z].xval,
        this.cutfromArray,
        this.orignalArrayLength - this.cutfromArray,
        0 + crop,
        width
      ); ///////
    }
  }

  if (this.axes.width != width || this.axes.height != this.spectrumHeight) {
    this.axes.width = width;
    this.axes.height = this.spectrumHeight;
    this.updateAxes();
  }
};

Spectrum.prototype.updateAxes = function () {
  var width = this.ctx_axes.canvas.width + 100; //width of x axis izz
  var height = this.ctx_axes.canvas.height;

  this.maxScaleX = this.centerHz + this.spanHz / 2;
  this.minScaleX = this.centerHz - this.spanHz / 2;

  // Clear and fill with black
  this.ctx_axes.fillStyle = "black";
  this.ctx_axes.fillRect(0, 0, width, height);

  // Draw axes
  this.ctx_axes.font = "12px Arial";
  this.ctx_axes.fillStyle = "white";
  this.ctx_axes.textBaseline = "middle";

  this.ctx_axes.textAlign = "center"; //change izz
  var step = 10; //steps for y-axis
  for (var i = this.max_db - 10; i >= this.min_db + 10; i -= step) {
    var y = height - this.squeeze(i, 0, height);
    this.ctx_axes.fillText(i, 20, y); // height - y

    this.ctx_axes.beginPath();
    this.ctx_axes.moveTo(22, y); //y axis stroked set izz
    this.ctx_axes.lineTo(width, y);
    this.ctx_axes.strokeStyle = "rgba(255, 255, 255, 0.15)"; //changed strokes izz
    this.ctx_axes.stroke();
  }

  this.ctx_axes.textBaseline = "bottom";
  // change X-axis
  var x_axisSteps = 10;
  for (var i = 0; i < x_axisSteps; i++) {
    var x = Math.round((width - crop) / x_axisSteps - 1) * i + crop;

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

      //Graph points in whole points

      var freq = this.centerHz + (this.spanHz / 10) * (i - 5);

      if (freq > 1e9) {
        freq = freq / 1e9;
        if (this.countDecimals(freq) > 4) {
          freq = freq.toFixed(0);
        }
        freq = freq + " GHz";
      } else if (freq > 1e6) {
        freq = freq / 1e6;
        if (this.countDecimals(freq) > 4) {
          freq = freq.toFixed(0);
        }
        freq = freq + " MHz"; //this function executed
      } else {
        if (this.countDecimals(freq) > 2) {
          freq = freq.toFixed(0);
        }
        freq = freq + " MHz";
      }
      this.ctx_axes.fillText(freq, x - 130, height); // x axia height change izz plus values placment
    }

    //console.log("ctx_axes : ", this.ctx_axes);

    this.ctx_axes.beginPath();
    this.ctx_axes.moveTo(x, 0);
    this.ctx_axes.lineTo(x, height);
    this.ctx_axes.strokeStyle = "rgba(200, 200, 200, 0.2)"; //straight gridline on x axis izza
    this.ctx_axes.stroke();
  }
  // const input = prompt("What's your name?");
};

Spectrum.prototype.toggleColor = function () {
  this.colorindex++;
  if (this.colorindex >= colormaps.length) this.colorindex = 0;
  this.colormap = colormaps[this.colorindex];
  this.updateSpectrumRatio();
};

Spectrum.prototype.setCenterHz = function (hz) {
  this.centerHz = hz;
  if (this.center != 0 && this.center != this.centerHz) {
    this.centerHz = this.center;
  }
  this.updateAxes();
};
Spectrum.prototype.setSpanHz = function (hz) {
  this.orignalSpanHz = hz;
  this.spanHz = hz;
  if (this.zoom != 0 && this.spanHz != this.zoom) {
    this.spanHz = this.zoom;
  }
  this.updateAxes();
};

Spectrum.prototype.squeeze = function (value, out_min, out_max) {
  if (value <= this.min_db) {
    return out_min;
  } else if (value >= this.max_db) {
    return out_max;
  } else {
    return Math.round(
      ((value - this.min_db) / (this.max_db - this.min_db)) * out_max
    );
  }
};
Spectrum.prototype.squeeze2 = function (value, out_min, out_max) {
  if (value <= 30000000) {
    return out_min;
  } else if (value >= 300000000) {
    return out_max;
  } else {
    return Math.round(
      ((value - 30000000) / (300000000 - 30000000)) * out_max
    );
  }
};

Spectrum.prototype.drawRectSpectrogram = function (y, h) {
  this.ctx.beginPath();
  this.ctx.fillStyle = colors[this.rectColor]; //"rgba(60, 229, 42, 0.31)"; //rect color
  this.ctx.strokeStyle = "green";
  this.ctx.rect(this.clickRectX, y, this.clickRectWidth, h);
  this.ctx.stroke();
  this.ctx.fill();
  this.ctx.closePath();
};

Spectrum.prototype.threshold = function (y, width, color) {
  this.ctx.beginPath();
  this.ctx.strokeStyle = color;
  this.ctx.lineWidth = 2; //threshold line width

  this.ctx.beginPath();
  this.ctx.moveTo(0, y);
  this.ctx.lineTo(width + crop, y);
  this.ctx.stroke();
  this.ctx.closePath();
  this.ctx.lineWidth = 1;
};

function drawPoint2(can, x, y, label) {
  can.beginPath();
  can.arc(x, y, 5, 0, 2 * Math.PI);
  can.fillStyle = "red";
  can.fill();

  can.fillText(label, x + 10, y);
  can.stroke(); // it creates X axies and
}

Spectrum.prototype.drawSpectrum = function (bins) {
  //get canvas height and width to draw spectrum ayaz
  var width = this.ctx.canvas.width;
  var height = this.ctx.canvas.height;

  //console.log("spectrum width and  height  : "+width+" "+ height);

  // width of Green Color ayaz
  this.ctx.lineWidth = 1; //Amplitude green line width .

  //All of the points are 119 nut few points are 118 and few 120 ayaz

  if (!this.binsAverage || this.binsAverage.length != bins.length) {
    //console.log('this.binsAverage  ', this.binsAverage );
    this.binsAverage = bins;
  } else {
    for (var i = 0; i < bins.length; i++) {
      this.binsAverage[i] +=
        (1 - this.averaging) * (bins[i] - this.binsAverage[i]);
    }
  }
  bins = this.binsAverage;

  //Do not draw anything if spectrum is not visible
  if (this.ctx_axes.canvas.height < 1) return;

  //////////////////////////////////////////////////////////////Creation of X and Y axis completed

  // Copy axes from offscreen canvas

  this.ctx.drawImage(this.ctx_axes.canvas, -1, -1); // create Spectrums X and Y axis

  // // Scale for FFT
  this.ctx.save(); // saves sppectrum previous stage

  //////////////////////////////////////////////////////////////Creation of X and Y axis completed

  this.ctx.scale(width / this.wf_size, 1); //green line visiblity izza
  var peakY = this.spectrumHeight;

  // Draw FFT bins
  this.ctx.beginPath();

  this.offset = Math.round(
    this.map(crop, 0, this.ctx.canvas.width + 6000, 0, this.wf_size)
  ); // green line axis set izza

  //console.log('this.offset : ', this.offset);
  console.log(
    "X and Y are : ",
    -1 + this.offset + "  " + this.spectrumHeight + 0
  );
  this.ctx.moveTo(-1 + this.offset, this.spectrumHeight + 0); //change for waterfall izza
  // above line is the address of left bottom corner of spectrum

  // console.log(
  //   "clkkkkkkkkkkkkkkk : ",
  //   this.clickRectX+
  //   crop+
  //   width+
  //   this.minScaleX+
  //   this.maxScaleX
  //   );

  var rangeMin = this.map(
    this.clickRectX,
    crop,
    width,
    this.minScaleX,
    this.maxScaleX
  );
  var rangeMax = this.map(
    this.clickRectWidth + this.clickRectX,
    crop,
    width,
    this.minScaleX,
    this.maxScaleX
  );

  if (rangeMin > rangeMax) {
    var temp;
    temp = rangeMax;
    rangeMax = rangeMin;
    rangeMin = temp;
  }

  var max = 100000000;

  this.scaleY = this.map(this.threshY, this.min_db, this.max_db, height / 2, 0); // 0, height/2 // height of threshold izza

  this.detectedFrequency.length = 0;

  // console.log('bins are ', bins);
  //console.log('new array ');
  for (var i = 0; i < bins.length; i++) {
    // if ( parseFloat( bins[i]) > -100 ) {
    //   console.log("bins : ", bins[i]);
    //   const input = prompt("What's your name?");
    //   alert(`Your name is ${input}`);
    // }
    var y =
      this.spectrumHeight - 1 - this.squeeze(bins[i], 0, this.spectrumHeight);
    peakY = this.squeeze(bins[i], 0, this.spectrumHeight);

    if (y > this.spectrumHeight - 1) {
      y = this.spectrumHeight - 1;
      console.log("Y has chnaged : ", y);
    }
    if (y < 0) {
      y = 0;
      console.log("y=0");
    }
    if (i == 0) {
      this.ctx.lineTo(-1 + this.offset, y);//responsible for height of green line
    }

    // green lines are created here ayaz
    // create green lines
    // important

    //drawPoint2(this.ctx ,i + this.offset, y);
    this.ctx.lineTo(i + this.offset, y); // that what point we start drawing green horizental green line

    // console.log(
    //   "Starting Line 1 i " +
    //     i +
    //     " X : " +
    //     parseInt(i + this.offset) +
    //     " y : " +
    //     y
    // );

    if (i == bins.length - 1) {
      //drawPoint2(this.ctx ,i + this.offset, y);
      this.ctx.lineTo(this.wf_size + 1 + this.offset, y);
      // console.log(
      //   "Second 2 i == bins.length - 1 i " +
      //     i +
      //     " Drawingg -1 + this.offset : " +
      //     parseInt(i + this.offset) +
      //     " y : " +
      //     y
      // );
    }

    for (var z = 0; z < this.tags.length; z++) {
      if (
        i + this.cutfromArray == this.tags[z].xval &&
        this.check_click == true
      ) {
        this.tags[z].tagY = y;
        this.tags[z].yval = Math.round(bins[i]);
        this.tags[z].displayX = Math.round(
          this.map(i, 0, bins.length, this.minScaleX, this.maxScaleX)
        );
      }
    }

    if (y < max) {
      max = y;
    }

    let newVal = Math.round(
      this.map(i, 0, bins.length, this.minScaleX, this.maxScaleX)
    );

    if (this.check_bar) {
      if (newVal < rangeMax && newVal > rangeMin) {
        if (y < this.scaleY) {
          var obj = new Object();
          obj.x = newVal;
          obj.y = Math.round(bins[i]);
          obj.count = 1;

          var check = true;

          for (var j = 0; j < this.threshRange.length; j++) {
            if (
              this.threshRange[j].x == obj.x &&
              this.threshRange[j].y == obj.y
            ) {
              this.threshRange[j].count++;
              check = false;
            }
          }
          if (check) {
            let tableRows = document
              .getElementById("thresh-table-body")
              .getElementsByTagName("tr").length;
            if (tableRows < 100) {
              this.threshRange.push(obj);
              // filling table
              let tbody = document.getElementById("thresh-table-body");
              let tr = document.createElement("tr");
              let td1 = document.createElement("td");
              let td2 = document.createElement("td");
              let td3 = document.createElement("td");
              td1.innerHTML = obj.x; //+" Hz"
              td2.innerHTML = obj.y;
              td3.innerHTML = obj.count;
              tr.appendChild(td1);
              tr.appendChild(td2);
              tr.appendChild(td3);
              tbody.appendChild(tr);
            }
          } else {
            // update table count
            for (let c = 0; c < this.threshRange.length; c++) {
              let tableRows =
                document.getElementById("thresh-table-body").rows[c].cells;
              if (
                tableRows[0].innerHTML == obj.x &&
                tableRows[1].innerHTML == obj.y
              ) {
                let countValue = Number(tableRows[2].innerHTML);
                countValue++;
                tableRows[2].innerHTML = countValue;
              }
            }
          }
        }
      }
    } else {
      if (y < this.scaleY) {
        var obj = new Object();
        obj.x = newVal;
        obj.y = Math.round(bins[i]);
        obj.count = 1;

        var check = true;

        for (var j = 0; j < this.threshRange.length; j++) {
          if (
            this.threshRange[j].x == obj.x &&
            this.threshRange[j].y == obj.y
          ) {
            this.threshRange[j].count++;
            check = false;
          }
        }
      }
    }
  }

  // this.ctx.beginPath();
  // this.ctx.arc(100, 75, 20, 0, 1 * Math.PI);
  // this.ctx.stroke();

  //this.ctx.strokeRect(1800,10, 40, 190);
  function containsObject(obj, list) {
    var i;
    for (i = 0; i < list.length; i++) {
      if (list[i] === obj) {
        return true;
      }
    }

    return false;
  }

  this.ctx.fillStyle = "rgba(0, 0, 0, 0.11)";

  // drawPoint2(this.ctx ,
  //   this.wf_size + 1 + this.offset,
  //   this.spectrumHeight + 1
  // );
  this.ctx.lineTo(this.wf_size + 1 + this.offset, this.spectrumHeight + 1);



  // console.log(
  //   "thired 3 this.wf_size + 1 + this.offset : " +
  //     parseInt(this.wf_size + 1 + this.offset) +
  //     " this.spectrumHeight + 1 : " +
  //     parseInt(this.spectrumHeight + 1)
  // );

  // drawPoint2(this.ctx ,
  //   this.wf_size + this.offset,
  //   this.spectrumHeight
  // );
  this.ctx.lineTo(this.wf_size + this.offset, this.spectrumHeight );

  // console.log(
  //   "Forth this.wf_size + this.offset : " +
  //     parseInt(this.wf_size + this.offset) +
  //     " y : " +
  //     y
  // );

  if (y < 230 && y > 245) {
    console.log("foundddddddddddddd");
  }

  this.ctx.closePath();

  this.ctx.restore();

  this.ctx.strokeStyle = "#259a00"; //color of spectrum green

  this.ctx.stroke(); // it creates X axies and
  /////////////////////////////////////////////////////////////////////////green ended

  if (this.spectrumColorCheck) {
    this.ctx.fillStyle = this.gradient; //chnage color of under line chart
  } else {
    this.ctx.fillStyle = "rgba(0, 0, 0, 0.0)";
  }

  this.ctx.fill();
  if (this.check_bar) {
    this.drawRectSpectrogram(0, height);
  }

  var colorTh = "#cc8315"; //By uncomment Change the threshold line color change
  this.threshold(this.scaleY, width, colorTh); // yellow light

  if (this.check_click == true) {
    for (let c = 0; c < this.tags.length; c++) {
      this.drawTag(
        this.tags[c].StayX,
        this.tags[c].tagY,
        this.tags[c].displayX,
        this.tags[c].yval
      );
    }
    if (this.removeTagCheck == true) {
      closeEnough = 30;
      for (var z = 0; z < this.tags.length; z++) {
        if (this.checkCloseEnough(this.StayX, this.tags[z].StayX + 15)) {
          this.tags.splice(z, 1);
          z--;
        }
      }
    }
  }

  closeEnough = 5;

  // span hz commented
  if (this.updateValueCheck) {
    if (this.countDecimals(this.threshY) > 2) {
      this.threshY = this.threshY.toFixed(2);
    }
    this.updateValueCheck = false;
  }

  var arrt = [ -100000000 , 40000000,35000000];

  this.spectrumWidth = Math.round(
    (this.canvas.width * this.spectrumPercent) / 100.0
  );

  for (var k = 0; k < 4; k++) {
    var alpha =
      this.spectrumHeight - 620 - this.squeeze2(arrt[k], 0, this.spectrumWidth);
    drawPoint2(this.ctx, alpha + 600, 150, "A");
  }

  // draw separate lines
  // for (var k=0;k< 4; k++){
  //   drawPoint2(this.ctx, "200000000", "-80");
  //   drawPoint2(this.ctx,"100000000", "-80");

  //   drawPoint2(this.ctx,"35000000", "-80");

  // }

  //console.log('this.ctx : ',this.ctx);
};

Spectrum.prototype.addData = function (data, bmp) {
  if (!this.paused) {
    if (data.length > 32768) {
      data = this.sequenceResize(data, 32767);
    }

    this.orignalArrayLength = data.length;

    if (this.orignalSpanHz > this.spanHz) {
      data = data.slice(
        this.cutfromArray,
        this.orignalArrayLength - this.cutfromArray
      );
    }

    if (data.length != this.wf_size) {
      this.wf_size = data.length;
      if (data.length > 32767) {
        this.ctx_wf.canvas.width = 32767;
      } else {
        this.ctx_wf.canvas.width = data.length;
      }

      this.ctx_wf.fillStyle = "black";
      this.ctx_wf.fillRect(0, 0, this.wf.width, 0); //strokes of waterfall

      for (var z = 0; z < this.tags.length; z++) {
        this.tags[z].StayX = this.map(
          this.tags[z].xval,
          this.cutfromArray,
          this.orignalArrayLength - this.cutfromArray,
          crop,
          this.ctx.canvas.width
        );
      }
    }

    this.drawSpectrum(data);
    // this.addWaterfallRowBmp(bmp);
    // this.addWaterfallRow(data);
    this.resize();
  }
};

function Spectrum(id, options) {
  // Handle options
  this.centerHz = options && options.centerHz ? options.centerHz : 0;
  this.spanHz = options && options.spanHz ? options.spanHz : 0;
  this.wf_size = options && options.wf_size ? options.wf_size : 0;
  this.wf_rows = options && options.wf_rows ? options.wf_rows : 50;
  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.rectColor = options && options.rectColor ? options.rectColor : 0;

  // Setup state
  this.paused = false;
  this.fullscreen = true;
  this.min_db = -140;
  this.max_db = -20;
  this.spectrumHeight = 0;

  // Colors
  this.colorindex = 2;
  this.colormap = colormaps[0];

  // Create main canvas and adjust dimensions to match actual
  this.canvas = document.getElementById("spectrumSM");
  canvas.height = this.canvas.clientHeight;
  this.canvas.width = this.canvas.clientWidth;

  this.ctx = this.canvas.getContext("2d");
  this.checkclick = false;
  this.clickRectWidth = 1000;
  this.dragL = true;
  this.dragR = true;
  // this.ctx.globalAlpha = 0.1;
  this.drag = true;
  this.click_x = 0;
  this.click_y = 0;
  this.check_click = true;
  this.threshY = -70;
  this.StayX = -10;
  this.scaleY = 0;

  //for change waterfall design
  this.threshCheck = true;
  this.removeTagCheck = true;
  this.spectrumColorCheck = true;
  this.check_bar = true;
  this.updateValueCheck = true;

  this.tags = [];
  this.addTagsCheck = true;

  this.maxScaleX = 0;
  this.minScaleX = 0;
  this.orignalArrayLength = 0;
  this.zoom = this.spanHz;
  this.center = this.centerHz;

  this.threshRange = [];
  this.detectedFrequency = [];

  this.offset = 10;
  this.arraySizeto = 0;
  this.orignalSpanHz = 0;
  this.cutfromArray = 0;

  this.ctx.fillStyle = "black";
  this.ctx.fillRect(10, 10, this.canvas.width, this.canvas.height);

  // Create offscreen canvas for axes
  this.axes = document.createElement("canvas");
  this.axes.id = "myCheck";
  this.axes.height = 1; // Updated later
  this.axes.width = this.canvas.width;
  this.ctx_axes = this.axes.getContext("2d");

  function myFunction() {
    this.style.fontSize = "40px";
  }

  // Create offscreen canvas for waterfall
  this.wf = document.createElement("canvas");
  this.wf.height = this.wf_rows;
  this.wf.width = this.wf_size;
  this.ctx_wf = this.wf.getContext("2d");

  // Trigger first render
  this.updateSpectrumRatio();
  this.resize();
}

Here showing value of spectrum at specific point via click and zoom in/ zoom out functionality.

var closeEnough = 5;
var crop = 150;
var data_sb = -10;
var canvas = document.getElementById("spectrumSM");

Spectrum.prototype.mousedown = function (evt) {
  this.checkclick = true;
  if (this.checkCloseEnough(this.click_x-3, this.clickRectX)) {
    this.dragR = false;
    this.dragL = true;
  } else if (
    this.checkCloseEnough(this.click_x-3, this.clickRectX + this.clickRectWidth)
  ) {
    this.dragR = true;
    this.dragL = false;
  } else if (this.checkCloseEnough(this.click_y-3, this.scaleY)) {
    this.drag = true;
  }
};
Spectrum.prototype.mouseup = function (evt) {
  this.checkclick = false;

  this.dragL = false;
  this.dragR = false;

  this.drag = false;

  if (evt.button === "right") {
    this.tags = [];
  }
};

Spectrum.prototype.mousemove = function (evt) {
  var rect = this.canvas.getBoundingClientRect();

  this.click_x = evt.clientX - rect.left;

  this.click_y = evt.clientY - rect.top;
  closeEnough = Math.abs(this.clickRectWidth);
  if (this.dragL == false && this.dragR == false && this.drag == false) {
    if (
      this.checkclick == true &&
      this.checkCloseEnough(
        this.click_x,
        this.clickRectX + this.clickRectWidth / 2
      )
    ) {
      this.clickRectX = this.click_x - this.clickRectWidth / 2;
    }
  } else if (this.dragL) {
    this.clickRectWidth += this.clickRectX - this.click_x;
    this.clickRectX = this.click_x;
  } else if (this.dragR) {
    this.clickRectWidth = -(this.clickRectX - this.click_x);
  } else if (this.drag && this.threshCheck) {
    this.updateValueCheck = true;
    this.threshY = this.map(
      this.click_y,
      this.canvas.height / 2,
      0,
      this.min_db,
      this.max_db
    ); // this.max_db, this.min_db
  }
  closeEnough = 10;
};

Spectrum.prototype.click = function (evt) {
  // change izza
  this.check_click = true;
  this.StayX = this.click_x;
  console.log('tag list : ',this.addTagsCheck);
  if (this.addTagsCheck == true && this.StayX > 3) {
    var tag = {
      StayX: this.click_x,
      tagY: 0,
      yval: 0,
      xval: Math.round(
        this.map(
          this.click_x,
          28,
          this.ctx.canvas.width,
          this.cutfromArray,
          this.orignalArrayLength - this.cutfromArray
        )
      ),
      displayX: 0,
    };

    this.tags.push(tag);
  }
};

Spectrum.prototype.wheel = function (evt) {
  this.zoom = this.spanHz;
  var inc;
  if (this.arraySizeto == 0) {
    inc = Math.round(this.orignalArrayLength * 0.05);
  } else {
    inc = Math.round(this.arraySizeto * 0.05);
  }
  let zoomInc = 0;
  if (this.orignalSpanHz > this.orignalArrayLength) {
    zoomInc = this.orignalSpanHz / this.orignalArrayLength;
  } else {
    zoomInc = this.orignalArrayLength / this.orignalSpanHz;
  }

  if (evt.deltaY > 0) {
    if (
      this.orignalSpanHz - (zoomInc * this.cutfromArray - inc * 2) <
        this.orignalSpanHz &&
      this.cutfromArray - inc >= 0
    ) {
      this.cutfromArray = this.cutfromArray - inc;
      this.zoom = this.orignalSpanHz - zoomInc * this.cutfromArray * 2;
    } else if (
      this.orignalSpanHz + zoomInc * this.cutfromArray * 2 >=
        this.orignalSpanHz &&
      this.cutfromArray - inc <= 0
    ) {
      this.zoom = this.orignalSpanHz;
      this.cutfromArray = 0;
    }
  } else if (evt.deltaY < 0) {
    if (
      this.orignalSpanHz - (zoomInc * this.cutfromArray + inc * 2) > inc &&
      this.orignalArrayLength - this.cutfromArray * 2 - inc * 2 >
        this.ctx.canvas.width
    ) {
      this.cutfromArray = this.cutfromArray + inc;
      this.zoom = this.orignalSpanHz - zoomInc * this.cutfromArray * 2;
    }
  }
  this.arraySizeto = this.orignalArrayLength - this.cutfromArray * 2;
  this.maxScaleX = this.centerHz + this.zoom / 20;
  this.minScaleX = this.centerHz - this.zoom / 20;
};

Spectrum.prototype.undoTag = function () {
  this.tags.pop();
};

Spectrum.prototype.checkCloseEnough = function (p1, p2) {
  return Math.abs(p1 - p2) < closeEnough;
};

Spectrum.prototype.drawTag = function (locx, locy, xval, yval) {
  this.ctx.beginPath();
  this.ctx.strokeStyle = "#cc8315";
  let freq = xval;
  if (freq > 1e9) {
    freq = freq / 1e9;
    if (this.countDecimals(freq) > 2) {
      freq = freq.toFixed(2);
    }
    freq = freq + " GHz";
  } else if (freq > 1e6) {
    freq = freq / 1e6;
    if (this.countDecimals(freq) > 2) {
      freq = freq.toFixed(2);
    }
    freq = freq + " MHz";
  } else {
    if (this.countDecimals(freq) > 2) {
      freq = freq.toFixed(2);
    }
  }
  var text = "  ( " + freq + ", " + yval + ")";
  var padding = 5;
  var fontSize = 20;
  var xPos = locx - padding;
  var width = this.ctx.measureText(text).width + padding * 2;
  var height = fontSize * 1.286;
  var yPos = locy - height / 1.5;
  this.ctx.lineWidth = 2;
  this.ctx.fillStyle = "rgba(204, 131, 21, 0.8)";
  // draw the rect
  this.ctx.fillRect(xPos, yPos, width, height);
  this.ctx.fillStyle = "white";
  this.ctx.font = "bold 10pt droid_serif";
  this.ctx.fillText(text, locx, locy);
  this.ctx.fillStyle = "white";
  this.ctx.beginPath();
  this.ctx.arc(locx, locy, 2, 0, Math.PI * 2, true);
  this.ctx.fill();
  this.ctx.closePath();
};
Ayaz
  • 67
  • 1
  • 9

1 Answers1

2

The spectrum project you reference expects to receive regular updates of an array of data passed into drawSpectrum. Each time it renders that data as a new spectrum in drawFFT, with the data scaled by the numbers set in setRange. Each time it receives data, it also creates a new row of pixels for the waterfall in the rowToImageData function, again scaled by the numbers set in setRange. The previously created rows of pixels are shifted down by one row in addWaterfallRow.

I've made a fiddle that shows how data is handled by the Spectrum object: https://jsfiddle.net/40qun892/

If you run the fiddle it shows two examples, one with three points and another with 100 points of randomly generated data. This line shows how an x-axis is added to the graph:

const spectrumB = new Spectrum("spectrumCanvasB", {spanHz: 5000, centerHz: 2500});

The x-axis is only shown when spanHz is defined. It is generated by drawing 11 labels, equally distributed across the canvas. With the center label based on centerHz, and the remaining labels calculated based on spanHz.

As you can see from the spectrums generated by the fiddle, the x-axis labels are not connected to the data, they are just equally distributed across the canvas.

The graph behind the data is created by applying a scale to the graph so that using the array index will result in data stretched across the graph.

this.ctx.scale(width / <number of data points>, 1);
for (var i = 0; i < <number of data points>.length; i++) {
    // lines removed
    this.ctx.lineTo(i, y);
    // lines removed
}

As you can see from the examples in the fiddle, this doesn't look very nice when there's only three datapoints, because the white lines are stretched in the x-direction but not the y-direction.

Spectrum doesn't care about x-coordinates. It just stretches whatever it is given to fit the canvas. If you give it a spanHz property, it will distribute some labels across the canvas too, but it does not associate them with the data.

The scaling seems to be slightly wrong in Spectrum (which is only noticeable if very few datapoints are used). If I make this change, the points are correctly stretched:

this.ctx.scale(width / (this.wf_size - 1), 1);

Then this change would set the x-axis labels to 100Mhz - 300Mhz:

const spectrumA = new Spectrum("spectrumCanvasA", {spanHz: 200000000, centerHz: 200000000});

Edit: (The relationship between frequency and data)

The only thing the code knows about the frequency is based on the spanHz anad centerHz.

The frequency at an array index is

(<array index> / <number of points on X axis> * <spanHz>) + (<centerHz> / 2)

The frequency at a pixel is

(<x coordinate> / <width of canvas> * <spanHz>) + (<centerHz> / 2)

If you want to convert a frequency to an array index (or a pixel) it would be slightly more complicated, because you would need to find the closest element to that frequency. For example, the pixel at a frequency is:

round((<frequency> - (<centerHz> / 2)) / <spanHz> * <width of canvas>)
aptriangle
  • 1,395
  • 1
  • 9
  • 11
  • yes, I know that I am asking that when we pass -70 how will it plot on the x-axis. which values on X-axis represent 50 Mhz (mega Hz) or 500mhz. I know the person uses ```this.offset``` values. but how does he synchronize pixel values and X-axis values? – Ayaz Jul 02 '22 at 09:03
  • The code is designed to have equally distributed x-axis values. this.wf_size = data.length; // wf_size is set to the number of data points passed in this.ctx.scale(width / this.wf_size, 1); // the displayed context is scaled so that the background rendering canvas will fill its width this.drawFFT(bins); // then the FFT is drawn with that scaling, so that it fills the whole width – aptriangle Jul 02 '22 at 13:00
  • In the drawSpectrum Method, i created 3 points at some points using drawdrawPoint2 Method. how can i create points on spectrum on 100MHz , 200MHZ and 300Mhz X-axis. i tried the above method but cannot position points on Exect value. in this example Y-axis value is contant. thank you so much for that you are guiding me. – Ayaz Jul 04 '22 at 07:51
  • 1
    answer updated with more details – aptriangle Jul 04 '22 at 09:13
  • thank you so much for reply. i know that The spectrum project you reference expects to receive regular updates of an array of data passed into drawSpectrum method and it creates the Spectrum. I am trying to plot another array on the same spectrum. first array is Recv in draw Spectrum Method. and second array is a temporary array var arrt = [ -100000000 , 40000000,35000000]; given in the question. i want to plot these points on the spectrum as red dots. – Ayaz Jul 04 '22 at 10:26
  • I am referencing the project you referenced. It does not currently have a way to calculate x-coordinates, because it uses stretching. To have non-stretched red dots, you will have to calculate x-coordinates yourself, based on this.centerHz, this.spanHz, and the size of the canvas. – aptriangle Jul 04 '22 at 10:43
  • or if you don't need to convert a frequency into an x-coordinate, just the width of the canvas divided by the number of points. – aptriangle Jul 04 '22 at 10:46
  • can you guild me any way, how can i calculate X-coordinates. any formula or any link i should fallow. like where should i start. and so far thank you so much for all time and useful information. if i divide width of the canvas divided by the number of points. it will divide X-axis into part (into number of points equally distance point) . how will that help me to plot point on spectrum – Ayaz Jul 04 '22 at 10:48
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/246145/discussion-between-ayaz-and-aptriangle). – Ayaz Jul 04 '22 at 11:06
  • hello thank you for all help till now. i wanted to know is there any why we can match the X-axis with the actual values of spectrum ? right now X-axis is just ``` width of axis/ no of points on X axis```. is there any way we can match x-axis with the actual values of spectrum. thank you – Ayaz Jul 15 '22 at 06:33
  • can you kindly guide me how can i do that. thank you – Ayaz Jul 15 '22 at 10:06
  • 1
    I edited the answer to add formulas to calculate various things based on the frequency – aptriangle Jul 15 '22 at 10:16
  • thank you so much for all help. can you please me guide in these i have zoom in/ zoom out functionality but when i zoom in and out the redDot Disappears. please can you guide me. – Ayaz Jul 21 '22 at 06:12
  • what does `displayX: 0; does in Spectrum.prototype.click = function (evt)` i tried edit this value nothing happens. this tag is displayed on the right side where we click. when i click left most it goes out of screen. how can i move to tag left side when we click on right most side ( `Spectrum.prototype.drawTag = function (locx, locy, xval, yval)` ). – Ayaz Jul 21 '22 at 10:30