Skip to main content
added 136 characters in body
Source Link

comparison graph

var rules = [
  //["F", "F[+F]F[-F]F"]

  //["F", "F+F--F+F"]

  //["1", "1F1F"],
  //["0", "1F[+0][-0]"]

  //["F", "F+F-F-F+F"]

  ["F", "FF"],
  ["X", "F-[[X]+X]+F[+FX]-X"]
];
var angle = 10;
const times = [{
  y: [],
  line: {
    color: "blue",
    width: 4,
    shape: "line"
  },
    name: "Original draw()"
}, {
  y: [],
  line: {
    color: "green",
    width: 4,
    shape: "line"
  },
    name: "Simplified draw()"
}];

function LSystem(rules, angle, init, level) {
  this.rules = {};
  this.angle = angle * Math.PI / 180;
  this.parseRules(rules);
  this.path = this.generateLevel(init, level);
}
LSystem.prototype.parseRules = function(rules) {
  var self = this;
  rules.forEach(function(rule) {
    self.rules[rule[0]] = rule[1];
  });
};
LSystem.prototype.applyRule = function(input) {
  var output = "";
  for (var i = 0; i < input.length; i++) {
    var char = input[i];
    if (char in this.rules) {
      output += this.rules[char];
    } else {
      output += char;
    }
  }
  return output;
};
LSystem.prototype.generateLevel = function(input, level) {
  if (level == 0) return input;
  return this.generateLevel(this.applyRule(input), level - 1);
};
LSystem.prototype.draw = function(x, y) {
  var start = +new Date();
  var self = this;
  var path = this.path;
  var table = {
    "F": function(ctx) {
      ctx.moveTo(0, 0);
      ctx.lineTo(5, 0);
      ctx.translate(5, 0);
    },
    "+": function(ctx) {
      ctx.rotate(self.angle);
    },
    "-": function(ctx) {
      ctx.rotate(-self.angle);
    },
    "[": function(ctx) {
      ctx.save();
    },
    "]": function(ctx) {
      //ctx.stroke();
      ctx.restore();
      //ctx.beginPath();
    }
  };
  ctx.save();
  ctx.clearRect(0, 0, 500, 500);
  ctx.beginPath();
  ctx.moveTo(x + 0.5, y + 0.5);
  ctx.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < path.length; i++) {
    var char = path[i];
    var cmd = table[char];
    cmd && cmd(ctx);
  }
  ctx.stroke();
  ctx.restore();
  const end = +new Date();
  times[0].y.push((end - start));
  if (times[0].y.length > 1) {
    avg.innerText = times[0].y.reduce((a, b) => a + b) / times[0].y.length;
  }
};
LSystem.prototype.drawB = function(x, y) {
  var start = +new Date();
  var table = {
    "F": function() {
      ctxB.moveTo(0, 0);
      ctxB.lineTo(5, 0);
      ctxB.translate(5, 0);
    },
    "+": ctxB.rotate.bind(ctxB, this.angle),
    "-": ctxB.rotate.bind(ctxB, -this.angle),
    "[": ctxB.save.bind(ctxB),
    "]": ctxB.restore.bind(ctxB)
  };
  ctxB.save();
  ctxB.clearRect(0, 0, 500, 500);
  ctxB.beginPath();
  ctxB.moveTo(x + 0.5, y + 0.5);
  ctxB.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < this.path.length; i++) {
    var char = this.path[i];
    var cmd = table[char];
    cmd && cmd();
  }
  ctxB.stroke();
  ctxB.restore();
  const end = +new Date();
  times[1].y.push((end - start));
  if (times[1].y.length > 1) {
    avgB.innerText = times[1].y.reduce((a, b) => a + b) / times[1].y.length;
    avgB.classList.toggle('faster', parseFloat(avgB.innerText) < parseFloat(avg.innerText));
    avgB.classList.toggle('slower', parseFloat(avgB.innerText) > parseFloat(avg.innerText));
    avg.classList.toggle('faster', parseFloat(avg.innerText) < parseFloat(avgB.innerText));
    avg.classList.toggle('slower', parseFloat(avg.innerText) > parseFloat(avgB.innerText));
  }
}


// main
var ctx,
  ctxB,
  s = new LSystem(rules, angle, "X", 5),
  sB = new LSystem(rules, angle, "X", 5),
  output;
window.onload = function() {
  ctx = document.querySelector("canvas").getContext("2d");
  ctxB = document.getElementById("cnvB").getContext("2d");
  output = document.querySelector("pre");

  ctx.translate(50, 500);
  ctx.rotate(Math.PI / 180 * -65);
  ctx.translate(0, -250);

  ctxB.translate(50, 500);
  ctxB.rotate(Math.PI / 180 * -65);
  ctxB.translate(0, -250);

  //ctx.translate(250, 500);
  function d(t) {
    requestAnimationFrame(d);
    ctx.clearRect(0, 0, 500, 500);
    ctxB.clearRect(0, 0, 500, 500);
    s.draw(0, 250);
    sB.drawB(0, 250);

    s.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
    sB.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
  }
  output.textContent = s.path.length;
  requestAnimationFrame(d);
};
canvas {
  border: 1px solid black;
}

output.slower {
  background-color: red;
  color: white;
}

output.faster {
  background-color: green;
  color: white;
}
<div id="chart">
  <div id="comparison"></div>
</div>
<div><table>
 <tr><td> Avg time for draw<code>draw()</code></td><td> <output id="avg"></output> ms
</div>td></tr>
<div><tr><td>
  Avg time for drawmodified <code>draw() with Simplifications</code> <output</td><td><output id="avgB"></output> ms
</div>td></tr>
</table>
<canvas width="500" height="500"></canvas>
<canvas width="500" height="500" id="cnvB"></canvas>
<pre></pre>

Result

Below is a chart comparing the elapsed times of 100 calls to the original draw method and the modified draw method.

comparison chart

comparison graph

var rules = [
  //["F", "F[+F]F[-F]F"]

  //["F", "F+F--F+F"]

  //["1", "1F1F"],
  //["0", "1F[+0][-0]"]

  //["F", "F+F-F-F+F"]

  ["F", "FF"],
  ["X", "F-[[X]+X]+F[+FX]-X"]
];
var angle = 10;
const times = [{
  y: [],
  line: {
    color: "blue",
    width: 4,
    shape: "line"
  },
    name: "Original draw()"
}, {
  y: [],
  line: {
    color: "green",
    width: 4,
    shape: "line"
  },
    name: "Simplified draw()"
}];

function LSystem(rules, angle, init, level) {
  this.rules = {};
  this.angle = angle * Math.PI / 180;
  this.parseRules(rules);
  this.path = this.generateLevel(init, level);
}
LSystem.prototype.parseRules = function(rules) {
  var self = this;
  rules.forEach(function(rule) {
    self.rules[rule[0]] = rule[1];
  });
};
LSystem.prototype.applyRule = function(input) {
  var output = "";
  for (var i = 0; i < input.length; i++) {
    var char = input[i];
    if (char in this.rules) {
      output += this.rules[char];
    } else {
      output += char;
    }
  }
  return output;
};
LSystem.prototype.generateLevel = function(input, level) {
  if (level == 0) return input;
  return this.generateLevel(this.applyRule(input), level - 1);
};
LSystem.prototype.draw = function(x, y) {
  var start = +new Date();
  var self = this;
  var path = this.path;
  var table = {
    "F": function(ctx) {
      ctx.moveTo(0, 0);
      ctx.lineTo(5, 0);
      ctx.translate(5, 0);
    },
    "+": function(ctx) {
      ctx.rotate(self.angle);
    },
    "-": function(ctx) {
      ctx.rotate(-self.angle);
    },
    "[": function(ctx) {
      ctx.save();
    },
    "]": function(ctx) {
      //ctx.stroke();
      ctx.restore();
      //ctx.beginPath();
    }
  };
  ctx.save();
  ctx.clearRect(0, 0, 500, 500);
  ctx.beginPath();
  ctx.moveTo(x + 0.5, y + 0.5);
  ctx.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < path.length; i++) {
    var char = path[i];
    var cmd = table[char];
    cmd && cmd(ctx);
  }
  ctx.stroke();
  ctx.restore();
  const end = +new Date();
  times[0].y.push((end - start));
  if (times[0].y.length > 1) {
    avg.innerText = times[0].y.reduce((a, b) => a + b) / times[0].y.length;
  }
};
LSystem.prototype.drawB = function(x, y) {
  var start = +new Date();
  var table = {
    "F": function() {
      ctxB.moveTo(0, 0);
      ctxB.lineTo(5, 0);
      ctxB.translate(5, 0);
    },
    "+": ctxB.rotate.bind(ctxB, this.angle),
    "-": ctxB.rotate.bind(ctxB, -this.angle),
    "[": ctxB.save.bind(ctxB),
    "]": ctxB.restore.bind(ctxB)
  };
  ctxB.save();
  ctxB.clearRect(0, 0, 500, 500);
  ctxB.beginPath();
  ctxB.moveTo(x + 0.5, y + 0.5);
  ctxB.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < this.path.length; i++) {
    var char = this.path[i];
    var cmd = table[char];
    cmd && cmd();
  }
  ctxB.stroke();
  ctxB.restore();
  const end = +new Date();
  times[1].y.push((end - start));
  if (times[1].y.length > 1) {
    avgB.innerText = times[1].y.reduce((a, b) => a + b) / times[1].y.length;
    avgB.classList.toggle('faster', parseFloat(avgB.innerText) < parseFloat(avg.innerText));
    avgB.classList.toggle('slower', parseFloat(avgB.innerText) > parseFloat(avg.innerText));
    avg.classList.toggle('faster', parseFloat(avg.innerText) < parseFloat(avgB.innerText));
    avg.classList.toggle('slower', parseFloat(avg.innerText) > parseFloat(avgB.innerText));
  }
}


// main
var ctx,
  ctxB,
  s = new LSystem(rules, angle, "X", 5),
  sB = new LSystem(rules, angle, "X", 5),
  output;
window.onload = function() {
  ctx = document.querySelector("canvas").getContext("2d");
  ctxB = document.getElementById("cnvB").getContext("2d");
  output = document.querySelector("pre");

  ctx.translate(50, 500);
  ctx.rotate(Math.PI / 180 * -65);
  ctx.translate(0, -250);

  ctxB.translate(50, 500);
  ctxB.rotate(Math.PI / 180 * -65);
  ctxB.translate(0, -250);

  //ctx.translate(250, 500);
  function d(t) {
    requestAnimationFrame(d);
    ctx.clearRect(0, 0, 500, 500);
    ctxB.clearRect(0, 0, 500, 500);
    s.draw(0, 250);
    sB.drawB(0, 250);

    s.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
    sB.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
  }
  output.textContent = s.path.length;
  requestAnimationFrame(d);
};
canvas {
  border: 1px solid black;
}

output.slower {
  background-color: red;
  color: white;
}

output.faster {
  background-color: green;
  color: white;
}
<div id="chart">
  <div id="comparison"></div>
</div>
<div>
  Avg time for draw() <output id="avg"></output> ms
</div>
<div>
  Avg time for draw() with Simplifications <output id="avgB"></output> ms
</div>
<canvas width="500" height="500"></canvas>
<canvas width="500" height="500" id="cnvB"></canvas>
<pre></pre>

var rules = [
  //["F", "F[+F]F[-F]F"]

  //["F", "F+F--F+F"]

  //["1", "1F1F"],
  //["0", "1F[+0][-0]"]

  //["F", "F+F-F-F+F"]

  ["F", "FF"],
  ["X", "F-[[X]+X]+F[+FX]-X"]
];
var angle = 10;
const times = [{
  y: [],
  line: {
    color: "blue",
    width: 4,
    shape: "line"
  },
    name: "Original draw()"
}, {
  y: [],
  line: {
    color: "green",
    width: 4,
    shape: "line"
  },
    name: "Simplified draw()"
}];

function LSystem(rules, angle, init, level) {
  this.rules = {};
  this.angle = angle * Math.PI / 180;
  this.parseRules(rules);
  this.path = this.generateLevel(init, level);
}
LSystem.prototype.parseRules = function(rules) {
  var self = this;
  rules.forEach(function(rule) {
    self.rules[rule[0]] = rule[1];
  });
};
LSystem.prototype.applyRule = function(input) {
  var output = "";
  for (var i = 0; i < input.length; i++) {
    var char = input[i];
    if (char in this.rules) {
      output += this.rules[char];
    } else {
      output += char;
    }
  }
  return output;
};
LSystem.prototype.generateLevel = function(input, level) {
  if (level == 0) return input;
  return this.generateLevel(this.applyRule(input), level - 1);
};
LSystem.prototype.draw = function(x, y) {
  var start = +new Date();
  var self = this;
  var path = this.path;
  var table = {
    "F": function(ctx) {
      ctx.moveTo(0, 0);
      ctx.lineTo(5, 0);
      ctx.translate(5, 0);
    },
    "+": function(ctx) {
      ctx.rotate(self.angle);
    },
    "-": function(ctx) {
      ctx.rotate(-self.angle);
    },
    "[": function(ctx) {
      ctx.save();
    },
    "]": function(ctx) {
      //ctx.stroke();
      ctx.restore();
      //ctx.beginPath();
    }
  };
  ctx.save();
  ctx.clearRect(0, 0, 500, 500);
  ctx.beginPath();
  ctx.moveTo(x + 0.5, y + 0.5);
  ctx.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < path.length; i++) {
    var char = path[i];
    var cmd = table[char];
    cmd && cmd(ctx);
  }
  ctx.stroke();
  ctx.restore();
  const end = +new Date();
  times[0].y.push((end - start));
  if (times[0].y.length > 1) {
    avg.innerText = times[0].y.reduce((a, b) => a + b) / times[0].y.length;
  }
};
LSystem.prototype.drawB = function(x, y) {
  var start = +new Date();
  var table = {
    "F": function() {
      ctxB.moveTo(0, 0);
      ctxB.lineTo(5, 0);
      ctxB.translate(5, 0);
    },
    "+": ctxB.rotate.bind(ctxB, this.angle),
    "-": ctxB.rotate.bind(ctxB, -this.angle),
    "[": ctxB.save.bind(ctxB),
    "]": ctxB.restore.bind(ctxB)
  };
  ctxB.save();
  ctxB.clearRect(0, 0, 500, 500);
  ctxB.beginPath();
  ctxB.moveTo(x + 0.5, y + 0.5);
  ctxB.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < this.path.length; i++) {
    var char = this.path[i];
    var cmd = table[char];
    cmd && cmd();
  }
  ctxB.stroke();
  ctxB.restore();
  const end = +new Date();
  times[1].y.push((end - start));
  if (times[1].y.length > 1) {
    avgB.innerText = times[1].y.reduce((a, b) => a + b) / times[1].y.length;
    avgB.classList.toggle('faster', parseFloat(avgB.innerText) < parseFloat(avg.innerText));
    avgB.classList.toggle('slower', parseFloat(avgB.innerText) > parseFloat(avg.innerText));
    avg.classList.toggle('faster', parseFloat(avg.innerText) < parseFloat(avgB.innerText));
    avg.classList.toggle('slower', parseFloat(avg.innerText) > parseFloat(avgB.innerText));
  }
}


// main
var ctx,
  ctxB,
  s = new LSystem(rules, angle, "X", 5),
  sB = new LSystem(rules, angle, "X", 5),
  output;
window.onload = function() {
  ctx = document.querySelector("canvas").getContext("2d");
  ctxB = document.getElementById("cnvB").getContext("2d");
  output = document.querySelector("pre");

  ctx.translate(50, 500);
  ctx.rotate(Math.PI / 180 * -65);
  ctx.translate(0, -250);

  ctxB.translate(50, 500);
  ctxB.rotate(Math.PI / 180 * -65);
  ctxB.translate(0, -250);

  //ctx.translate(250, 500);
  function d(t) {
    requestAnimationFrame(d);
    ctx.clearRect(0, 0, 500, 500);
    ctxB.clearRect(0, 0, 500, 500);
    s.draw(0, 250);
    sB.drawB(0, 250);

    s.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
    sB.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
  }
  output.textContent = s.path.length;
  requestAnimationFrame(d);
};
canvas {
  border: 1px solid black;
}

output.slower {
  background-color: red;
  color: white;
}

output.faster {
  background-color: green;
  color: white;
}
<div id="chart">
  <div id="comparison"></div>
</div>
<table>
 <tr><td> Avg time for <code>draw()</code></td><td> <output id="avg"></output> ms
</td></tr>
<tr><td>
  Avg time for modified <code>draw()</code> </td><td><output id="avgB"></output> ms
</td></tr>
</table>
<canvas width="500" height="500"></canvas>
<canvas width="500" height="500" id="cnvB"></canvas>
<pre></pre>

Result

Below is a chart comparing the elapsed times of 100 calls to the original draw method and the modified draw method.

comparison chart

deleted 795 characters in body
Source Link
var rules = [
  //["F", "F[+F]F[-F]F"]

  //["F", "F+F--F+F"]

  //["1", "1F1F"],
  //["0", "1F[+0][-0]"]

  //["F", "F+F-F-F+F"]

  ["F", "FF"],
  ["X", "F-[[X]+X]+F[+FX]-X"]
];
var angle = 10;
const times = [{
  y: [],
  line: {
    color: "blue",
    width: 4,
    shape: "line"
  },
    name: "Original draw()"
}, {
  y: [],
  line: {
    color: "green",
    width: 4,
    shape: "line"
  },
    name: "Simplified draw()"
}];

function LSystem(rules, angle, init, level) {
  this.rules = {};
  this.angle = angle * Math.PI / 180;
  this.parseRules(rules);
  this.path = this.generateLevel(init, level);
}
LSystem.prototype.parseRules = function(rules) {
  var self = this;
  rules.forEach(function(rule) {
    self.rules[rule[0]] = rule[1];
  });
};
LSystem.prototype.applyRule = function(input) {
  var output = "";
  for (var i = 0; i < input.length; i++) {
    var char = input[i];
    if (char in this.rules) {
      output += this.rules[char];
    } else {
      output += char;
    }
  }
  return output;
};
LSystem.prototype.generateLevel = function(input, level) {
  if (level == 0) return input;
  return this.generateLevel(this.applyRule(input), level - 1);
};
LSystem.prototype.draw = function(x, y) {
  var start = +new Date();
  var self = this;
  var path = this.path;
  var table = {
    "F": function(ctx) {
      ctx.moveTo(0, 0);
      ctx.lineTo(5, 0);
      ctx.translate(5, 0);
    },
    "+": function(ctx) {
      ctx.rotate(self.angle);
    },
    "-": function(ctx) {
      ctx.rotate(-self.angle);
    },
    "[": function(ctx) {
      ctx.save();
    },
    "]": function(ctx) {
      //ctx.stroke();
      ctx.restore();
      //ctx.beginPath();
    }
  };
  ctx.save();
  ctx.clearRect(0, 0, 500, 500);
  ctx.beginPath();
  ctx.moveTo(x + 0.5, y + 0.5);
  ctx.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < path.length; i++) {
    var char = path[i];
    var cmd = table[char];
    cmd && cmd(ctx);
  }
  ctx.stroke();
  ctx.restore();
  const end = +new Date();
  times[0].y.push((end - start));
  if (times[0].y.length > 1) {
    avg.innerText = times[0].y.reduce((a, b) => a + b) / times[0].y.length;
  }
};
LSystem.prototype.drawB = function(x, y) {
  var start = +new Date();
  var table = {
    "F": function() {
      ctxB.moveTo(0, 0);
      ctxB.lineTo(5, 0);
      ctxB.translate(5, 0);
    },
    "+": ctxB.rotate.bind(ctxB, this.angle),
    "-": ctxB.rotate.bind(ctxB, -this.angle),
    "[": ctxB.save.bind(ctxB),
    "]": ctxB.restore.bind(ctxB)
  };
  ctxB.save();
  ctxB.clearRect(0, 0, 500, 500);
  ctxB.beginPath();
  ctxB.moveTo(x + 0.5, y + 0.5);
  ctxB.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < this.path.length; i++) {
    var char = this.path[i];
    var cmd = table[char];
    cmd && cmd();
  }
  ctxB.stroke();
  ctxB.restore();
  const end = +new Date();
  times[1].y.push((end - start));
  if (times[1].y.length > 1) {
    avgB.innerText = times[1].y.reduce((a, b) => a + b) / times[1].y.length;
    avgB.classList.toggle('faster', parseFloat(avgB.innerText) < parseFloat(avg.innerText));
    avgB.classList.toggle('slower', parseFloat(avgB.innerText) > parseFloat(avg.innerText));
    avg.classList.toggle('faster', parseFloat(avg.innerText) < parseFloat(avgB.innerText));
    avg.classList.toggle('slower', parseFloat(avg.innerText) > parseFloat(avgB.innerText));
  }
}


// main
var ctx,
  ctxB,
  s = new LSystem(rules, angle, "X", 5),
  sB = new LSystem(rules, angle, "X", 5),
  output;
window.onload = function() {
  ctx = document.querySelector("canvas").getContext("2d");
  ctxB = document.getElementById("cnvB").getContext("2d");
  output = document.querySelector("pre");

  ctx.translate(50, 500);
  ctx.rotate(Math.PI / 180 * -65);
  ctx.translate(0, -250);

  ctxB.translate(50, 500);
  ctxB.rotate(Math.PI / 180 * -65);
  ctxB.translate(0, -250);

  //ctx.translate(250, 500);
  function d(t) {
    requestAnimationFrame(d);
    ctx.clearRect(0, 0, 500, 500);
    ctxB.clearRect(0, 0, 500, 500);
    s.draw(0, 250);
    sB.drawB(0, 250);

    s.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
    sB.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
  }
  output.textContent = s.path.length;
  requestAnimationFrame(d);
};
const layout = {
  title: 'Comparison of original draw and modified draw method timing',
  xaxis: {
    title: 'time'
  },
  yaxis: {
    title: 'avg. draw time'
  }
};
const chartApp = new Vue({
  el: '#chart',
  data() {
    return {
      times: times
    }
  },
  mounted() {
    Plotly.plot('comparison', times, layout);
  },
  watch: {
    times: {
      handler: function() {
        Plotly.react('comparison', times, layout);
      },
      deep: true
    }
  }
});
canvas {
  border: 1px solid black;
}

output.slower {
  background-color: red;
  color: white;
}

output.faster {
  background-color: green;
  color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/plotly.js/1.39.1/plotly.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="chart">
  <div id="comparison"></div>
</div>
<div>
  Avg time for draw() <output id="avg"></output> ms
</div>
<div>
  Avg time for draw() with Simplifications <output id="avgB"></output> ms
</div>
<canvas width="500" height="500"></canvas>
<canvas width="500" height="500" id="cnvB"></canvas>
<pre></pre>
var rules = [
  //["F", "F[+F]F[-F]F"]

  //["F", "F+F--F+F"]

  //["1", "1F1F"],
  //["0", "1F[+0][-0]"]

  //["F", "F+F-F-F+F"]

  ["F", "FF"],
  ["X", "F-[[X]+X]+F[+FX]-X"]
];
var angle = 10;
const times = [{
  y: [],
  line: {
    color: "blue",
    width: 4,
    shape: "line"
  },
    name: "Original draw()"
}, {
  y: [],
  line: {
    color: "green",
    width: 4,
    shape: "line"
  },
    name: "Simplified draw()"
}];

function LSystem(rules, angle, init, level) {
  this.rules = {};
  this.angle = angle * Math.PI / 180;
  this.parseRules(rules);
  this.path = this.generateLevel(init, level);
}
LSystem.prototype.parseRules = function(rules) {
  var self = this;
  rules.forEach(function(rule) {
    self.rules[rule[0]] = rule[1];
  });
};
LSystem.prototype.applyRule = function(input) {
  var output = "";
  for (var i = 0; i < input.length; i++) {
    var char = input[i];
    if (char in this.rules) {
      output += this.rules[char];
    } else {
      output += char;
    }
  }
  return output;
};
LSystem.prototype.generateLevel = function(input, level) {
  if (level == 0) return input;
  return this.generateLevel(this.applyRule(input), level - 1);
};
LSystem.prototype.draw = function(x, y) {
  var start = +new Date();
  var self = this;
  var path = this.path;
  var table = {
    "F": function(ctx) {
      ctx.moveTo(0, 0);
      ctx.lineTo(5, 0);
      ctx.translate(5, 0);
    },
    "+": function(ctx) {
      ctx.rotate(self.angle);
    },
    "-": function(ctx) {
      ctx.rotate(-self.angle);
    },
    "[": function(ctx) {
      ctx.save();
    },
    "]": function(ctx) {
      //ctx.stroke();
      ctx.restore();
      //ctx.beginPath();
    }
  };
  ctx.save();
  ctx.clearRect(0, 0, 500, 500);
  ctx.beginPath();
  ctx.moveTo(x + 0.5, y + 0.5);
  ctx.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < path.length; i++) {
    var char = path[i];
    var cmd = table[char];
    cmd && cmd(ctx);
  }
  ctx.stroke();
  ctx.restore();
  const end = +new Date();
  times[0].y.push((end - start));
  if (times[0].y.length > 1) {
    avg.innerText = times[0].y.reduce((a, b) => a + b) / times[0].y.length;
  }
};
LSystem.prototype.drawB = function(x, y) {
  var start = +new Date();
  var table = {
    "F": function() {
      ctxB.moveTo(0, 0);
      ctxB.lineTo(5, 0);
      ctxB.translate(5, 0);
    },
    "+": ctxB.rotate.bind(ctxB, this.angle),
    "-": ctxB.rotate.bind(ctxB, -this.angle),
    "[": ctxB.save.bind(ctxB),
    "]": ctxB.restore.bind(ctxB)
  };
  ctxB.save();
  ctxB.clearRect(0, 0, 500, 500);
  ctxB.beginPath();
  ctxB.moveTo(x + 0.5, y + 0.5);
  ctxB.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < this.path.length; i++) {
    var char = this.path[i];
    var cmd = table[char];
    cmd && cmd();
  }
  ctxB.stroke();
  ctxB.restore();
  const end = +new Date();
  times[1].y.push((end - start));
  if (times[1].y.length > 1) {
    avgB.innerText = times[1].y.reduce((a, b) => a + b) / times[1].y.length;
    avgB.classList.toggle('faster', parseFloat(avgB.innerText) < parseFloat(avg.innerText));
    avgB.classList.toggle('slower', parseFloat(avgB.innerText) > parseFloat(avg.innerText));
    avg.classList.toggle('faster', parseFloat(avg.innerText) < parseFloat(avgB.innerText));
    avg.classList.toggle('slower', parseFloat(avg.innerText) > parseFloat(avgB.innerText));
  }
}


// main
var ctx,
  ctxB,
  s = new LSystem(rules, angle, "X", 5),
  sB = new LSystem(rules, angle, "X", 5),
  output;
window.onload = function() {
  ctx = document.querySelector("canvas").getContext("2d");
  ctxB = document.getElementById("cnvB").getContext("2d");
  output = document.querySelector("pre");

  ctx.translate(50, 500);
  ctx.rotate(Math.PI / 180 * -65);
  ctx.translate(0, -250);

  ctxB.translate(50, 500);
  ctxB.rotate(Math.PI / 180 * -65);
  ctxB.translate(0, -250);

  //ctx.translate(250, 500);
  function d(t) {
    requestAnimationFrame(d);
    ctx.clearRect(0, 0, 500, 500);
    ctxB.clearRect(0, 0, 500, 500);
    s.draw(0, 250);
    sB.drawB(0, 250);

    s.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
    sB.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
  }
  output.textContent = s.path.length;
  requestAnimationFrame(d);
};
const layout = {
  title: 'Comparison of original draw and modified draw method timing',
  xaxis: {
    title: 'time'
  },
  yaxis: {
    title: 'avg. draw time'
  }
};
const chartApp = new Vue({
  el: '#chart',
  data() {
    return {
      times: times
    }
  },
  mounted() {
    Plotly.plot('comparison', times, layout);
  },
  watch: {
    times: {
      handler: function() {
        Plotly.react('comparison', times, layout);
      },
      deep: true
    }
  }
});
canvas {
  border: 1px solid black;
}

output.slower {
  background-color: red;
  color: white;
}

output.faster {
  background-color: green;
  color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/plotly.js/1.39.1/plotly.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="chart">
  <div id="comparison"></div>
</div>
<div>
  Avg time for draw() <output id="avg"></output> ms
</div>
<div>
  Avg time for draw() with Simplifications <output id="avgB"></output> ms
</div>
<canvas width="500" height="500"></canvas>
<canvas width="500" height="500" id="cnvB"></canvas>
<pre></pre>
var rules = [
  //["F", "F[+F]F[-F]F"]

  //["F", "F+F--F+F"]

  //["1", "1F1F"],
  //["0", "1F[+0][-0]"]

  //["F", "F+F-F-F+F"]

  ["F", "FF"],
  ["X", "F-[[X]+X]+F[+FX]-X"]
];
var angle = 10;
const times = [{
  y: [],
  line: {
    color: "blue",
    width: 4,
    shape: "line"
  },
    name: "Original draw()"
}, {
  y: [],
  line: {
    color: "green",
    width: 4,
    shape: "line"
  },
    name: "Simplified draw()"
}];

function LSystem(rules, angle, init, level) {
  this.rules = {};
  this.angle = angle * Math.PI / 180;
  this.parseRules(rules);
  this.path = this.generateLevel(init, level);
}
LSystem.prototype.parseRules = function(rules) {
  var self = this;
  rules.forEach(function(rule) {
    self.rules[rule[0]] = rule[1];
  });
};
LSystem.prototype.applyRule = function(input) {
  var output = "";
  for (var i = 0; i < input.length; i++) {
    var char = input[i];
    if (char in this.rules) {
      output += this.rules[char];
    } else {
      output += char;
    }
  }
  return output;
};
LSystem.prototype.generateLevel = function(input, level) {
  if (level == 0) return input;
  return this.generateLevel(this.applyRule(input), level - 1);
};
LSystem.prototype.draw = function(x, y) {
  var start = +new Date();
  var self = this;
  var path = this.path;
  var table = {
    "F": function(ctx) {
      ctx.moveTo(0, 0);
      ctx.lineTo(5, 0);
      ctx.translate(5, 0);
    },
    "+": function(ctx) {
      ctx.rotate(self.angle);
    },
    "-": function(ctx) {
      ctx.rotate(-self.angle);
    },
    "[": function(ctx) {
      ctx.save();
    },
    "]": function(ctx) {
      //ctx.stroke();
      ctx.restore();
      //ctx.beginPath();
    }
  };
  ctx.save();
  ctx.clearRect(0, 0, 500, 500);
  ctx.beginPath();
  ctx.moveTo(x + 0.5, y + 0.5);
  ctx.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < path.length; i++) {
    var char = path[i];
    var cmd = table[char];
    cmd && cmd(ctx);
  }
  ctx.stroke();
  ctx.restore();
  const end = +new Date();
  times[0].y.push((end - start));
  if (times[0].y.length > 1) {
    avg.innerText = times[0].y.reduce((a, b) => a + b) / times[0].y.length;
  }
};
LSystem.prototype.drawB = function(x, y) {
  var start = +new Date();
  var table = {
    "F": function() {
      ctxB.moveTo(0, 0);
      ctxB.lineTo(5, 0);
      ctxB.translate(5, 0);
    },
    "+": ctxB.rotate.bind(ctxB, this.angle),
    "-": ctxB.rotate.bind(ctxB, -this.angle),
    "[": ctxB.save.bind(ctxB),
    "]": ctxB.restore.bind(ctxB)
  };
  ctxB.save();
  ctxB.clearRect(0, 0, 500, 500);
  ctxB.beginPath();
  ctxB.moveTo(x + 0.5, y + 0.5);
  ctxB.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < this.path.length; i++) {
    var char = this.path[i];
    var cmd = table[char];
    cmd && cmd();
  }
  ctxB.stroke();
  ctxB.restore();
  const end = +new Date();
  times[1].y.push((end - start));
  if (times[1].y.length > 1) {
    avgB.innerText = times[1].y.reduce((a, b) => a + b) / times[1].y.length;
    avgB.classList.toggle('faster', parseFloat(avgB.innerText) < parseFloat(avg.innerText));
    avgB.classList.toggle('slower', parseFloat(avgB.innerText) > parseFloat(avg.innerText));
    avg.classList.toggle('faster', parseFloat(avg.innerText) < parseFloat(avgB.innerText));
    avg.classList.toggle('slower', parseFloat(avg.innerText) > parseFloat(avgB.innerText));
  }
}


// main
var ctx,
  ctxB,
  s = new LSystem(rules, angle, "X", 5),
  sB = new LSystem(rules, angle, "X", 5),
  output;
window.onload = function() {
  ctx = document.querySelector("canvas").getContext("2d");
  ctxB = document.getElementById("cnvB").getContext("2d");
  output = document.querySelector("pre");

  ctx.translate(50, 500);
  ctx.rotate(Math.PI / 180 * -65);
  ctx.translate(0, -250);

  ctxB.translate(50, 500);
  ctxB.rotate(Math.PI / 180 * -65);
  ctxB.translate(0, -250);

  //ctx.translate(250, 500);
  function d(t) {
    requestAnimationFrame(d);
    ctx.clearRect(0, 0, 500, 500);
    ctxB.clearRect(0, 0, 500, 500);
    s.draw(0, 250);
    sB.drawB(0, 250);

    s.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
    sB.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
  }
  output.textContent = s.path.length;
  requestAnimationFrame(d);
};
canvas {
  border: 1px solid black;
}

output.slower {
  background-color: red;
  color: white;
}

output.faster {
  background-color: green;
  color: white;
}
<div id="chart">
  <div id="comparison"></div>
</div>
<div>
  Avg time for draw() <output id="avg"></output> ms
</div>
<div>
  Avg time for draw() with Simplifications <output id="avgB"></output> ms
</div>
<canvas width="500" height="500"></canvas>
<canvas width="500" height="500" id="cnvB"></canvas>
<pre></pre>
added 938 characters in body
Source Link

comparison graph

var rules = [
  //["F", "F[+F]F[-F]F"]

  //["F", "F+F--F+F"]

  //["1", "1F1F"],
  //["0", "1F[+0][-0]"]

  //["F", "F+F-F-F+F"]

  ["F", "FF"],
  ["X", "F-[[X]+X]+F[+FX]-X"]
];
var angle = 10;
const times = [{
  y: [],
  line: {
    color: "blue",
    width: 4,
    shape: "line"
  },
    name: "Original draw()"
}, {
  y: [],
  line: {
    color: "green",
    width: 4,
    shape: "line"
  },
    name: "Simplified draw()"
}];

function LSystem(rules, angle, init, level) {
  this.rules = {};
  this.angle = angle * Math.PI / 180;
  this.parseRules(rules);
  this.path = this.generateLevel(init, level);
}
LSystem.prototype.parseRules = function(rules) {
  var self = this;
  rules.forEach(function(rule) {
    self.rules[rule[0]] = rule[1];
  });
};
LSystem.prototype.applyRule = function(input) {
  var output = "";
  for (var i = 0; i < input.length; i++) {
    var char = input[i];
    if (char in this.rules) {
      output += this.rules[char];
    } else {
      output += char;
    }
  }
  return output;
};
LSystem.prototype.generateLevel = function(input, level) {
  if (level == 0) return input;
  return this.generateLevel(this.applyRule(input), level - 1);
};
LSystem.prototype.draw = function(x, y) {
  var start = +new Date();
  var self = this;
  var path = this.path;
  var table = {
    "F": function(ctx) {
      ctx.moveTo(0, 0);
      ctx.lineTo(5, 0);
      ctx.translate(5, 0);
    },
    "+": function(ctx) {
      ctx.rotate(self.angle);
    },
    "-": function(ctx) {
      ctx.rotate(-self.angle);
    },
    "[": function(ctx) {
      ctx.save();
    },
    "]": function(ctx) {
      //ctx.stroke();
      ctx.restore();
      //ctx.beginPath();
    }
  };
  ctx.save();
  ctx.clearRect(0, 0, 500, 500);
  ctx.beginPath();
  ctx.moveTo(x + 0.5, y + 0.5);
  ctx.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < path.length; i++) {
    var char = path[i];
    var cmd = table[char];
    cmd && cmd(ctx);
  }
  ctx.stroke();
  ctx.restore();
  const end = +new Date();
  times[0].y.push((end - start));
  if (times[0].y.length > 1) {
    avg.innerText = times[0].y.reduce((a, b) => a + b) / times[0].y.length;
  }
};
LSystem.prototype.drawB = function(x, y) {
  var start = +new Date();
  var table = {
    "F": function() {
      ctxB.moveTo(0, 0);
      ctxB.lineTo(5, 0);
      ctxB.translate(5, 0);
    },
    "+": ctxB.rotate.bind(ctxB, this.angle),
    "-": ctxB.rotate.bind(ctxB, -this.angle),
    "[": ctxB.save.bind(ctxB),
    "]": ctxB.restore.bind(ctxB)
  };
  ctxB.save();
  ctxB.clearRect(0, 0, 500, 500);
  ctxB.beginPath();
  ctxB.moveTo(x + 0.5, y + 0.5);
  ctxB.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < this.path.length; i++) {
    var char = this.path[i];
    var cmd = table[char];
    cmd && cmd();
  }
  ctxB.stroke();
  ctxB.restore();
  const end = +new Date();
  times[1].y.push((end - start));
  if (times[1].y.length > 1) {
    avgB.innerText = times[1].y.reduce((a, b) => a + b) / times[1].y.length;
  }
  /*if (timesB.length > 1) {
    avgB.innerText = timesB.reduce((a, b) => a + b) / timesB.length;
    avgB.classList.toggle('faster', parseFloat(avgB.innerText) < parseFloat(avg.innerText));
    avgB.classList.toggle('slower', parseFloat(avgB.innerText) > parseFloat(avg.innerText));
    avg.classList.toggle('faster', parseFloat(avg.innerText) < parseFloat(avgB.innerText));
    avg.classList.toggle('slower', parseFloat(avg.innerText) > parseFloat(avgB.innerText));
  }*/
}


// main
var ctx,
  ctxB,
  s = new LSystem(rules, angle, "X", 5),
  sB = new LSystem(rules, angle, "X", 5),
  output;
window.onload = function() {
  ctx = document.querySelector("canvas").getContext("2d");
  ctxB = document.getElementById("cnvB").getContext("2d");
  output = document.querySelector("pre");

  ctx.translate(50, 500);
  ctx.rotate(Math.PI / 180 * -65);
  ctx.translate(0, -250);

  ctxB.translate(50, 500);
  ctxB.rotate(Math.PI / 180 * -65);
  ctxB.translate(0, -250);

  //ctx.translate(250, 500);
  function d(t) {
    requestAnimationFrame(d);
    ctx.clearRect(0, 0, 500, 500);
    ctxB.clearRect(0, 0, 500, 500);
    s.draw(0, 250);
    sB.drawB(0, 250);

    s.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
    sB.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
  }
  output.textContent = s.path.length;
  requestAnimationFrame(d);
};
const layout = {
  title: 'Comparison of original draw and modified draw method timing',
  xaxis: {
    title: 'time'
  },
  yaxis: {
    title: 'avg. draw time'
  }
};
const chartApp = new Vue({
  el: '#chart',
  data() {
    return {
      times: times
    }
  },
  mounted() {
    Plotly.plot('comparison', times, layout);
  },
  watch: {
    times: {
      handler: function() {
        Plotly.react('comparison', times, layout);
      },
      deep: true
    }
  }
});
canvas {
  border: 1px solid black;
}

output.slower {
  background-color: red;
  color: white;
}

output.faster {
  background-color: green;
  color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/plotly.js/1.39.1/plotly.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="chart">
  <div id="comparison"></div>
</div>
<div>
  Avg time for draw() <output id="avg"></output> ms
</div>
<div>
  Avg time for draw() with Simplifications <output id="avgB"></output> ms
</div>
<canvas width="500" height="500"></canvas>
<canvas width="500" height="500" id="cnvB"></canvas>
<pre></pre>

var rules = [
  //["F", "F[+F]F[-F]F"]

  //["F", "F+F--F+F"]

  //["1", "1F1F"],
  //["0", "1F[+0][-0]"]

  //["F", "F+F-F-F+F"]

  ["F", "FF"],
  ["X", "F-[[X]+X]+F[+FX]-X"]
];
var angle = 10;
const times = [{
  y: [],
  line: {
    color: "blue",
    width: 4,
    shape: "line"
  }
}, {
  y: [],
  line: {
    color: "green",
    width: 4,
    shape: "line"
  }
}];

function LSystem(rules, angle, init, level) {
  this.rules = {};
  this.angle = angle * Math.PI / 180;
  this.parseRules(rules);
  this.path = this.generateLevel(init, level);
}
LSystem.prototype.parseRules = function(rules) {
  var self = this;
  rules.forEach(function(rule) {
    self.rules[rule[0]] = rule[1];
  });
};
LSystem.prototype.applyRule = function(input) {
  var output = "";
  for (var i = 0; i < input.length; i++) {
    var char = input[i];
    if (char in this.rules) {
      output += this.rules[char];
    } else {
      output += char;
    }
  }
  return output;
};
LSystem.prototype.generateLevel = function(input, level) {
  if (level == 0) return input;
  return this.generateLevel(this.applyRule(input), level - 1);
};
LSystem.prototype.draw = function(x, y) {
  var start = +new Date();
  var self = this;
  var path = this.path;
  var table = {
    "F": function(ctx) {
      ctx.moveTo(0, 0);
      ctx.lineTo(5, 0);
      ctx.translate(5, 0);
    },
    "+": function(ctx) {
      ctx.rotate(self.angle);
    },
    "-": function(ctx) {
      ctx.rotate(-self.angle);
    },
    "[": function(ctx) {
      ctx.save();
    },
    "]": function(ctx) {
      //ctx.stroke();
      ctx.restore();
      //ctx.beginPath();
    }
  };
  ctx.save();
  ctx.clearRect(0, 0, 500, 500);
  ctx.beginPath();
  ctx.moveTo(x + 0.5, y + 0.5);
  ctx.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < path.length; i++) {
    var char = path[i];
    var cmd = table[char];
    cmd && cmd(ctx);
  }
  ctx.stroke();
  ctx.restore();
  const end = +new Date();
  times[0].y.push((end - start));
  if (times[0].y.length > 1) {
    avg.innerText = times[0].y.reduce((a, b) => a + b) / times[0].y.length;
  }
};
LSystem.prototype.drawB = function(x, y) {
  var start = +new Date();
  var table = {
    "F": function() {
      ctxB.moveTo(0, 0);
      ctxB.lineTo(5, 0);
      ctxB.translate(5, 0);
    },
    "+": ctxB.rotate.bind(ctxB, this.angle),
    "-": ctxB.rotate.bind(ctxB, -this.angle),
    "[": ctxB.save.bind(ctxB),
    "]": ctxB.restore.bind(ctxB)
  };
  ctxB.save();
  ctxB.clearRect(0, 0, 500, 500);
  ctxB.beginPath();
  ctxB.moveTo(x + 0.5, y + 0.5);
  ctxB.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < this.path.length; i++) {
    var char = this.path[i];
    var cmd = table[char];
    cmd && cmd();
  }
  ctxB.stroke();
  ctxB.restore();
  const end = +new Date();
  times[1].y.push((end - start));
  if (times[1].y.length > 1) {
    avgB.innerText = times[1].y.reduce((a, b) => a + b) / times[1].y.length;
  }
  /*if (timesB.length > 1) {
    avgB.innerText = timesB.reduce((a, b) => a + b) / timesB.length;
    avgB.classList.toggle('faster', parseFloat(avgB.innerText) < parseFloat(avg.innerText));
    avgB.classList.toggle('slower', parseFloat(avgB.innerText) > parseFloat(avg.innerText));
    avg.classList.toggle('faster', parseFloat(avg.innerText) < parseFloat(avgB.innerText));
    avg.classList.toggle('slower', parseFloat(avg.innerText) > parseFloat(avgB.innerText));
  }*/
}


// main
var ctx,
  ctxB,
  s = new LSystem(rules, angle, "X", 5),
  sB = new LSystem(rules, angle, "X", 5),
  output;
window.onload = function() {
  ctx = document.querySelector("canvas").getContext("2d");
  ctxB = document.getElementById("cnvB").getContext("2d");
  output = document.querySelector("pre");

  ctx.translate(50, 500);
  ctx.rotate(Math.PI / 180 * -65);
  ctx.translate(0, -250);

  ctxB.translate(50, 500);
  ctxB.rotate(Math.PI / 180 * -65);
  ctxB.translate(0, -250);

  //ctx.translate(250, 500);
  function d(t) {
    requestAnimationFrame(d);
    ctx.clearRect(0, 0, 500, 500);
    ctxB.clearRect(0, 0, 500, 500);
    s.draw(0, 250);
    sB.drawB(0, 250);

    s.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
    sB.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
  }
  output.textContent = s.path.length;
  requestAnimationFrame(d);
};
const layout = {
  title: 'Comparison of original draw and modified draw method timing',
  xaxis: {
    title: 'time'
  },
  yaxis: {
    title: 'avg. draw time'
  }
};
const chartApp = new Vue({
  el: '#chart',
  data() {
    return {
      times: times
    }
  },
  mounted() {
    Plotly.plot('comparison', times, layout);
  },
  watch: {
    times: {
      handler: function() {
        Plotly.react('comparison', times, layout);
      },
      deep: true
    }
  }
});
canvas {
  border: 1px solid black;
}

output.slower {
  background-color: red;
  color: white;
}

output.faster {
  background-color: green;
  color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/plotly.js/1.39.1/plotly.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="chart">
  <div id="comparison"></div>
</div>
<div>
  Avg time for draw() <output id="avg"></output> ms
</div>
<div>
  Avg time for draw() with Simplifications <output id="avgB"></output> ms
</div>
<canvas width="500" height="500"></canvas>
<canvas width="500" height="500" id="cnvB"></canvas>
<pre></pre>

comparison graph

var rules = [
  //["F", "F[+F]F[-F]F"]

  //["F", "F+F--F+F"]

  //["1", "1F1F"],
  //["0", "1F[+0][-0]"]

  //["F", "F+F-F-F+F"]

  ["F", "FF"],
  ["X", "F-[[X]+X]+F[+FX]-X"]
];
var angle = 10;
const times = [{
  y: [],
  line: {
    color: "blue",
    width: 4,
    shape: "line"
  },
    name: "Original draw()"
}, {
  y: [],
  line: {
    color: "green",
    width: 4,
    shape: "line"
  },
    name: "Simplified draw()"
}];

function LSystem(rules, angle, init, level) {
  this.rules = {};
  this.angle = angle * Math.PI / 180;
  this.parseRules(rules);
  this.path = this.generateLevel(init, level);
}
LSystem.prototype.parseRules = function(rules) {
  var self = this;
  rules.forEach(function(rule) {
    self.rules[rule[0]] = rule[1];
  });
};
LSystem.prototype.applyRule = function(input) {
  var output = "";
  for (var i = 0; i < input.length; i++) {
    var char = input[i];
    if (char in this.rules) {
      output += this.rules[char];
    } else {
      output += char;
    }
  }
  return output;
};
LSystem.prototype.generateLevel = function(input, level) {
  if (level == 0) return input;
  return this.generateLevel(this.applyRule(input), level - 1);
};
LSystem.prototype.draw = function(x, y) {
  var start = +new Date();
  var self = this;
  var path = this.path;
  var table = {
    "F": function(ctx) {
      ctx.moveTo(0, 0);
      ctx.lineTo(5, 0);
      ctx.translate(5, 0);
    },
    "+": function(ctx) {
      ctx.rotate(self.angle);
    },
    "-": function(ctx) {
      ctx.rotate(-self.angle);
    },
    "[": function(ctx) {
      ctx.save();
    },
    "]": function(ctx) {
      //ctx.stroke();
      ctx.restore();
      //ctx.beginPath();
    }
  };
  ctx.save();
  ctx.clearRect(0, 0, 500, 500);
  ctx.beginPath();
  ctx.moveTo(x + 0.5, y + 0.5);
  ctx.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < path.length; i++) {
    var char = path[i];
    var cmd = table[char];
    cmd && cmd(ctx);
  }
  ctx.stroke();
  ctx.restore();
  const end = +new Date();
  times[0].y.push((end - start));
  if (times[0].y.length > 1) {
    avg.innerText = times[0].y.reduce((a, b) => a + b) / times[0].y.length;
  }
};
LSystem.prototype.drawB = function(x, y) {
  var start = +new Date();
  var table = {
    "F": function() {
      ctxB.moveTo(0, 0);
      ctxB.lineTo(5, 0);
      ctxB.translate(5, 0);
    },
    "+": ctxB.rotate.bind(ctxB, this.angle),
    "-": ctxB.rotate.bind(ctxB, -this.angle),
    "[": ctxB.save.bind(ctxB),
    "]": ctxB.restore.bind(ctxB)
  };
  ctxB.save();
  ctxB.clearRect(0, 0, 500, 500);
  ctxB.beginPath();
  ctxB.moveTo(x + 0.5, y + 0.5);
  ctxB.translate(x + 0.5, y + 0.5);
  for (var i = 0; i < this.path.length; i++) {
    var char = this.path[i];
    var cmd = table[char];
    cmd && cmd();
  }
  ctxB.stroke();
  ctxB.restore();
  const end = +new Date();
  times[1].y.push((end - start));
  if (times[1].y.length > 1) {
    avgB.innerText = times[1].y.reduce((a, b) => a + b) / times[1].y.length;
    avgB.classList.toggle('faster', parseFloat(avgB.innerText) < parseFloat(avg.innerText));
    avgB.classList.toggle('slower', parseFloat(avgB.innerText) > parseFloat(avg.innerText));
    avg.classList.toggle('faster', parseFloat(avg.innerText) < parseFloat(avgB.innerText));
    avg.classList.toggle('slower', parseFloat(avg.innerText) > parseFloat(avgB.innerText));
  }
}


// main
var ctx,
  ctxB,
  s = new LSystem(rules, angle, "X", 5),
  sB = new LSystem(rules, angle, "X", 5),
  output;
window.onload = function() {
  ctx = document.querySelector("canvas").getContext("2d");
  ctxB = document.getElementById("cnvB").getContext("2d");
  output = document.querySelector("pre");

  ctx.translate(50, 500);
  ctx.rotate(Math.PI / 180 * -65);
  ctx.translate(0, -250);

  ctxB.translate(50, 500);
  ctxB.rotate(Math.PI / 180 * -65);
  ctxB.translate(0, -250);

  //ctx.translate(250, 500);
  function d(t) {
    requestAnimationFrame(d);
    ctx.clearRect(0, 0, 500, 500);
    ctxB.clearRect(0, 0, 500, 500);
    s.draw(0, 250);
    sB.drawB(0, 250);

    s.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
    sB.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
  }
  output.textContent = s.path.length;
  requestAnimationFrame(d);
};
const layout = {
  title: 'Comparison of original draw and modified draw method timing',
  xaxis: {
    title: 'time'
  },
  yaxis: {
    title: 'avg. draw time'
  }
};
const chartApp = new Vue({
  el: '#chart',
  data() {
    return {
      times: times
    }
  },
  mounted() {
    Plotly.plot('comparison', times, layout);
  },
  watch: {
    times: {
      handler: function() {
        Plotly.react('comparison', times, layout);
      },
      deep: true
    }
  }
});
canvas {
  border: 1px solid black;
}

output.slower {
  background-color: red;
  color: white;
}

output.faster {
  background-color: green;
  color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/plotly.js/1.39.1/plotly.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="chart">
  <div id="comparison"></div>
</div>
<div>
  Avg time for draw() <output id="avg"></output> ms
</div>
<div>
  Avg time for draw() with Simplifications <output id="avgB"></output> ms
</div>
<canvas width="500" height="500"></canvas>
<canvas width="500" height="500" id="cnvB"></canvas>
<pre></pre>
added 938 characters in body
Source Link
Loading
added 27 characters in body
Source Link
Loading
added 6 characters in body
Source Link
Loading
added 46 characters in body
Source Link
Loading
added 516 characters in body
Source Link
Loading
add benchmark to provide proof
Source Link
Loading
Source Link
Loading