Search code examples
javascriptmathsetcomplex-numbersviewer

Julia Set Viewer


I've been trying to make a julia set viewer over at my site http://thejamespaterson.com/scripts/julia/, but I'm currently having trouble getting the program to display the correct julia set. For example, when testing with C value 0+0i, I get the following image: not a proper julia set The result is supposed to be a circle. I'm not sure why this is happening. I wrote my own complex numbers library and plotting functions, and they are posted below. Any help would be appreciated;

function complexNum(real, imaginary) {
  this.real = real;
  this.imaginary = imaginary;
  return this;
}

function addComplex(c1, c2) {
  this.real = c1.real + c2.real;
  this.imaginary = c1.imaginary + c2.imaginary;
  return this;
}

function multComplex(c1, c2) {
  this.real = (c1.real * c2.real) - (c1.imaginary * c2.imaginary);
  this.imaginary = (c1.real * c2.imaginary) + (c2.real * c1.imaginary);
  return this;
}

function dispComplex(c) {
  var sign = '';
  if (c.imaginary >= 0) {
    sign = '+';
  }
  return c.real + sign + c.imaginary + "i";
}

function getComplexModulus(c) {
  return Math.sqrt((c.real * c.real) + (c.imaginary * c.imaginary));
}

//globals
var MAXITERATION = 100;
var BOUNDARY = 4;
var CANVASID = "juliaDraw";
var CONTEXT = document.getElementById("juliaDraw").getContext('2d');
var HEIGHT = 750;
var WIDTH = 750;
var juliaImageData = CONTEXT.createImageData(WIDTH, HEIGHT);

function readInput(inputID) {
  return document.getElementById(inputID).value;
}

function drawPointOnCanvas(x, y, color) {
  //console.log('drawing pixel at '+x+','+y);
  CONTEXT.fillStyle = color;
  CONTEXT.fillRect(x, y, 1, 1);
}

function createArray(length) {
  var arr = new Array(length || 0),
    i = length;
  if (arguments.length > 1) {
    var args = Array.prototype.slice.call(arguments, 1);
    while (i--) arr[length - 1 - i] = createArray.apply(this, args);
  }
  return arr;
}

function doesPointEscape(c, complexNum) {
  var iterations = 0;
  var escaped = false;
  while ((!escaped) && (iterations < MAXITERATION)) {
    if (getComplexModulus(complexNum) > BOUNDARY) {
      escaped = true;
    }
    complexNum = addComplex(multComplex(complexNum, complexNum), c);
    iterations++;
  }
  if (escaped) {
    return true;
  } else {
    return false;
  }
}

function plotJuliaSet(canvasID, width, height, c, start, stepsize) {
  var complexNumberArray = createArray(width + 1, height + 1);
  var doesPointEscapeArray = createArray(width + 1, height + 1);
  var real = start.real;
  var imaginary = start.imaginary;
  console.log('====Drawing Set====');
  console.log('c = ' + dispComplex(c));
  for (var x = 0; x <= width; x++) {
    imaginary = start.imaginary;
    for (var y = 0; y <= height; y++) {
      complexNumberArray[x][y] = new complexNum(real, imaginary);
      doesPointEscapeArray[x][y] = doesPointEscape(c, complexNumberArray[x][y]);
      if (doesPointEscapeArray[x][y]) {
        //drawPointOnCanvas(x, y,'blue');
      } else {
        drawPointOnCanvas(x, y, 'black');
        //console.log('point '+dispComplex(complexNumberArray[x][y])+' does not escape');
      }
      imaginary = imaginary - stepsize;
    }
    real = real + stepsize;
  }
  //CONTEXT.putImageData(juliaImageData, 0, 0);
  console.log('done');
}

function defaultDraw() {
  CONTEXT.clearRect(0, 0, WIDTH, HEIGHT);
  var start = new complexNum(-2, 2);
  var c = new complexNum(0, 0);
  plotJuliaSet(CANVASID, WIDTH, HEIGHT, c, start, 2 / 350);
}

function drawJulia() {
  CONTEXT.clearRect(0, 0, WIDTH, HEIGHT);
  var start = new complexNum(-2, 2);
  var c = new complexNum(readInput('realValue') * 1, readInput('imagValue') * 1);
  plotJuliaSet(CANVASID, WIDTH, HEIGHT, c, start, 2 / 350);
}
<!doctype html>
<html>

<head>
  <title>Julia Set Viewer</title>
  <style>
    .desc {
      float: right;
      width: 300px;
    }
    #juliaDraw {
      border: 1px dotted;
      float: left;
    }
  </style>
</head>

<body>
  <div class="desc">
    <h1>Julia Set Viewer</h1>
    <p>You can view Julia sets with this simple online tool. Don't know what a Julia set is? Learn about it <a href="https://www.youtube.com/watch?v=2AZYZ-L8m9Q">here.</a>
      This script uses a complex number library that I built to handle the arithmetic required to process these images. The source code is hosted on my <a href="https://github.com/jamjar919">github.</a>
    </p>
  </div>
  <canvas id="juliaDraw" width=750 height=750 onClick="defaultDraw()"></canvas>
  <div class="controls">
    <form>
      <label>Real:
        <input type="text" id="realValue" value="0">
      </label>
      <label>Imag:
        <input type="text" id="imagValue" value="0">
      </label>
      <input type="button" onClick="drawJulia()">
    </form>
  </div>
  <script src="complex.js"></script>
  <script src="juliaset.js"></script>
</body>

</html>


Solution

  • The problem stems from confusion over the use of the this pointer in Javascript.

    Change your Julia calculation in doesPointEscape() to

    complexNum = new addComplex(new multComplex(complexNum, complexNum), c);
    

    and it works.

    This will return a new complex number from multComplex, then add it to c and return a new complex number from addComplex that is assigned to complexNum.

    Your multComplex and addComplex functions use the this pointer, but for the this pointer to be referring to one of your complex numbers, you would have to be calling the function on an existing one, or calling new to create a new one.

    Alternatively, you could rewrite your multComplex() and addComplex() functions as

    function multComplex(c1, c2) {
        var real = (c1.real * c2.real) - (c1.imaginary * c2.imaginary);
        var imaginary = (c1.real * c2.imaginary) + (c2.real * c1.imaginary);
        return new ComplexNum(real, imaginary);
    }
    
    function addComplex(c1, c2) {
        var real = c1.real + c2.real;
        var imaginary = c1.imaginary + c2.imaginary;
        return new ComplexNum(real, imaginary);
    }
    

    then your doesPointEscape() function should work as-is.