0

I have been trying to draw gaussin-like function using bezierCurveTo

find the code below

<canvas id="thisCan" width="0px" height="0px" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>

<script>
(function() {
var
// Obtain a reference to the canvas element
// using its id.
htmlCanvas = document.getElementById('thisCan'),
   // Obtain a graphics context on the
   // canvas element for drawing.
   ctx = htmlCanvas.getContext('2d');

var width = 0;
var height = 0;

// Start listening to resize events and
// draw canvas.
initialize();

function initialize() 
{
  // Register an event listener to
  // call the resizeCanvas() function each time
  // the window is resized.
  window.addEventListener('resize', resizeCanvas, false);
  // Draw canvas border for the first time.
  resizeCanvas();
}

// Display custom canvas.
// In this case it's a blue, 5 pixel border that
// resizes along with the browser window.
function redraw()
{
  ctx.beginPath();

  ctx.moveTo(width/2, 0);
  ctx.bezierCurveTo(150, 119, 186, 121, 66, 185);

  ctx.moveTo(width/2 + width * 0.08 , 0);
  ctx.bezierCurveTo(344, 119, 344, 121, 504, 185);

  ctx.stroke();
}

// Runs each time the DOM window resize event fires.
// Resets the canvas dimensions to match window,
// then draws the new borders accordingly.
function resizeCanvas()
{
  var contentElement = document.getElementsByClassName("content-box post")[0]
  width = htmlCanvas.width = (contentElement.clientWidth * 0.75)
  height = htmlCanvas.height = (contentElement.offsetWidth*0.75 * 0.5);

  redraw();
}

})();
</script>

I am planning to draw many curves in between as well. But how do I make it parametric, based on width and height variables?

I need to specify the control points using width and height parameters, so that it becomes window-size invariant.

Is there a way?

Adorn
  • 1,403
  • 1
  • 23
  • 46
  • Excuse me, can you show full code and describe what you want. P.S. don't use global variables =) just send `width` and `height` to `redraw()` and _Use the `requestAnimationFrame()`, Luke_ instead `resize` (more about it https://developer.mozilla.org/en-US/docs/Web/Events/resize) – Rudolf Manusachi Feb 12 '16 at 08:05
  • @RudolfManusadzhyan, thank you for valuable knowledge sharing. Today only I have started learning javascript, so I'll remember - no_globals! With `requestAnimationFrame()`, I am not looking for animation. I just need to make sure my graph is not teared when window is resized. So I think resize() is more appropriate, correct me if I am wrong. btw, i solved the issue – Adorn Feb 12 '16 at 09:44
  • Question: why? If you want to draw a gaussian, just sample the curve and draw it by connecting the dots (literally, by running through the array of points and lineTo'ing from one point to the next). Using Bezier curves doesn't make a whole lot of sense here. – Mike 'Pomax' Kamermans Feb 12 '16 at 17:17

2 Answers2

2

Don't use Bezier curves if you want an exponent curve, they're different functions. Instead, just plot the function you actually need, with something like http://jsbin.com/nubutodosu/edit?js,output, where you define a Gaussian object:

// Gaussian distribution generator

var Gaussian = function(mean, std) {
  this.mean = mean;
  this.std = std;
  this.a = 1/Math.sqrt(2*Math.PI);
};

Gaussian.prototype = {
  addStd: function(v) {
    this.std += v;
  },

  get: function(x) {
    var f = this.a / this.std;
    var p = -1/2;
    var c = (x-this.mean)/this.std;
    c *= c;
    p *= c;
    return f * Math.pow(Math.E, p);
  },

  generateValues: function(start, end) {
    var LUT = [];
    var step = (Math.abs(start)+Math.abs(end)) / 100;
    for(var i=start; i<end; i+=step) {
      LUT.push(this.get(i));
    }
    return LUT;
  }
};

And then you can give that a draw routine so that it can plot itself over the interval that you need:

...
  draw: function(ctx) {
    var points = this.generateValues(-10,10);
    var len = points.length;
    ctx.strokeStyle = "black";
    ctx.beginPath();
    var p0 = points[0];
    ctx.moveTo(0, height - (height*p0));
    points.forEach(function(p,i) {
      if(i===0) {
        return;
      }
      ctx.lineTo(width * i/len, height - (height*p));
      p0 = p;
    });
    ctx.stroke();
  }
...

So you build your array of values over the interval, then draw them on the canvas by "connecting the dots".

Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
-2

I managed to resolve it.

To be specific, I was looking out for half-gaussian curve.

I managed to figure out that guassian function has a very special property with respect to bezier curves. It might be elementary, but I could not find it on google. So this might be informative finding.

If each control points of the cubic bezier curve reside on a "line parallel to X axis and passing through start point/end point", the resulting curve will be of a half guassian shape.

e.g.

enter image description here

On top of this finding, I have written the following code:

<canvas id="thisCan" width="0px" height="0px">
Your browser does not support the HTML5 canvas tag.
</canvas>

<script>
(function() {
var
// Obtain a reference to the canvas element
// using its id.
htmlCanvas = document.getElementById('thisCan'),
   // Obtain a graphics context on the
   // canvas element for drawing.
   ctx = htmlCanvas.getContext('2d');

// Start listening to resize events and
// draw canvas.
initialize();

function initialize() 
{
  // Register an event listener to
  // call the resizeCanvas() function each time
  // the window is resized.
  window.addEventListener('resize', resizeCanvas, false);
  // Draw canvas border for the first time.
  resizeCanvas();
}

// Display custom canvas.
// In this case it's a blue, 5 pixel border that
// resizes along with the browser window.
function redraw(width, height)
{
  var start = width/2;
  var margin = width * 0.01;
  var height = (width / 3) - (margin * 4);
  var end_step = width/4

  ctx.beginPath();

  ctx.moveTo(start - margin, 0);
  ctx.bezierCurveTo((start - height/3), 0 , (start - height/3), height , end_step*1, height);

  ctx.moveTo(start + margin, 0);
  ctx.bezierCurveTo((start + height/3), 0 , (start + height/3), height , end_step*3, height);

  ctx.moveTo(start - margin, 0);
  ctx.bezierCurveTo((start - height*0.33), 0 , (start - height*0.16), height , end_step*1.5, height);

  ctx.moveTo(start + margin, 0);
  ctx.bezierCurveTo((start + height*0.33), 0 , (start + height*0.16), height , end_step*2.5, height);

  ctx.moveTo(start, 0);
  ctx.bezierCurveTo((start ), 0 , (start ), height , end_step*2, height);

  ctx.stroke();
}

// Runs each time the DOM window resize event fires.
// Resets the canvas dimensions to match window,
// then draws the new borders accordingly.
function resizeCanvas()
{
  var width = 0;
  var height = 0;

  var contentElement = document.getElementsByClassName("content-box post")[0]
  width = htmlCanvas.width = (contentElement.clientWidth * 0.85)
  height = htmlCanvas.height = (contentElement.offsetWidth*0.85 * 0.33);

  redraw(width, height);
}

})();
</script>

and the output:

enter image description here

Adorn
  • 1,403
  • 1
  • 23
  • 46
  • 1
    No, it won't. The Gaussian function runs from zero at negative infinity to zero at infinity. It will never evaluate to zero for any real value `x`. And, since the Gaussian runs the entire real number line, "half" a Gaussian is still infinitely long, so using it to represent the curve you're showing is using the wrong terms. You're just showing the curve cut off at a relatively high number of standard deviations from the mean. It looks very much like what you're actually trying to plot is a [sigmoid](https://en.wikipedia.org/wiki/Sigmoid_function), which does run from 0 to 1. – Mike 'Pomax' Kamermans Feb 12 '16 at 17:19