jQuery animations with a MxN matrix - javascript

I'm splitting a element into multiple blocks (defined by a number of rows and columns), and then fade these blocks to create animation effects. The type of animation is decided by the delay() value:
$('.block').each(function (i) {
$(this).stop().delay(30 * i).animate({
'opacity': 1
}, {
duration: 420
});
});
In this case each block's fade effect is delayed by (30 * current block index). The first block gets 0 delay, the second block 30 delay, ..... the last block 30 * (number of blocks) delay. So this will fade all blocks horizontally.
I've posted a list of effects I've come up so far here: http://jsfiddle.net/MRPDw/.
What I need help with is to find the delay expression for a spiral type effect, and maybe others that you think are possible :D

Here is an example of code for a spiral pattern:
case 'spiral':
$('.block', grid).css({
'opacity': 0
});
var order = new Array();
var rows2 = rows/2, x, y, z, n=0;
for (z = 0; z < rows2; z++){
y = z;
for (x = z; x < cols - z - 1; x++) {
order[n++] = y * cols + x;
}
x = cols - z - 1;
for (y = z; y < rows - z - 1; y++) {
order[n++] = y * cols + x;
}
y = rows - z - 1;
for (x = cols - z - 1; x > z; x--) {
order[n++] = y * cols + x;
}
x = z;
for (y = rows - z - 1; y > z; y--) {
order[n++] = y * cols + x;
}
}
for (var m = 0; m < n; m++) {
$('.block-' + order[m], grid).stop().delay(100*m).animate({
opacity: 1
}, {
duration: 420,
complete: (m != n - 1) ||
function () {
alert('done');
}
});
}
break;
See it working in this fiddle.
I also improved on your "RANDOM" animation, to show all the squares, not just a subset. The code for that is:
case 'random':
var order = new Array();
var numbers = new Array();
var x, y, n=0, m=0, ncells = rows*cols;
for (y = 0; y < rows; y++){
for (x = 0; x < cols; x++){
numbers[n] = n++;
}
}
while(m < ncells){
n = Math.floor(Math.random()*ncells);
if (numbers[n] != -1){
order[m++] = n;
numbers[n] = -1;
}
}
$('.block', grid).css({
'opacity': 0
});
for (var m = 0; m < ncells; m++) {
$('.block-' + order[m], grid).stop().delay(100*m).animate({
opacity: 1
}, {
duration: 420,
complete: (m != ncells - 1) ||
function () {
alert('done');
}
});
}
break;
See it working in this fiddle.

Maybe the easiest way to think about making a spiral animation, is to think about your matrix as a piece of paper.
If you fold 2 times that paper in the x and y center axes, you end up getting a smaller square (or rectangle) quadrant.
Now, if you animate this quadrant only from bottom right to top left corner (in the same way you did for your 'diagonal-reverse'), you can propagate this movement to the other 3 quadrants in order to get the final effect of having an animation running from the center of your matrix up to the four corners.
case 'spiral':
$('.block', grid).css({
'opacity': 0
});
n = 0;
var center = {
x: cols / 2,
y: rows / 2
};
// iterate on the second quadrant only
for (var y = 0; y < center.y; y++)
for (var x = 0; x < center.x; x++) {
// and apply the animation to all quadrants, by using the multiple jQuery selector
$('.block-' + (y * rows + x) + ', ' + // 2nd quadrant
'.block-' + (y * rows + cols - x - 1) + ', ' + // 1st quadrant
'.block-' + ((rows - y - 1) * rows + x) + ', ' + // 3rd quadrant
'.block-' + ((rows - y - 1) * rows + cols - x - 1) // 4th quadrant
, grid).stop().delay(100 * (center.y - y + center.x - x)).animate({
opacity: 1
}, {
duration: 420,
complete: function () {
if (++n == rows * cols) {
alert('done'); // fire next animation...
}
}
});
}
Here is the demo (click the spiral link)

Related

Island detection: javscript

The problem
I am making a javascript island generator using DFS algorithm, but I ran into a problem. I want to detect the position and area of each island, and not all the points that lie within the island. It creates a new island for every single coordinate above sea level. Here is my code:
// recursive function to check the island's size
checkIsland(island, x, y){
if (this.terrain.getHeightFromMap(x, y) >= 3) {
island.size++;
if (x > 0) {
this.checkIsland(island, x - 1, y);
this.matrix[`${x - 1}:${y}`] = 1;
}
if (x < this.terrain.length - 1) {
this.checkIsland(island, x + 1, y);
this.matrix[`${x + 1}:${y}`] = 1;
}
if (y > 0) {
this.checkIsland(island, x, y - 1);
this.matrix[`${x}:${y - 1}`] = 1;
}
if (y < this.terrain.length - 1) {
this.checkIsland(island, x, y + 1);
this.matrix[`${x}:${y + 1}`] = 1;
}
// update the matrix
this.matrix[`${x}:${y}`] = 1;
}
}
update(){
this.campos = [Math.round(this.camera.position.x), Math.round(this.camera.position.y), Math.round(this.camera.position.z)];
for (var x = -10+this.campos[0]; x < 10+this.campos[0]; x++) {
for (var y = -10+this.campos[2]; y < 10+this.campos[2]; y++) {
if(this.matrix[`${x}:${y}`] == undefined){
let island = Object.assign({}, island_example);
island.position = [x, y];
this.checkIsland(island, x, y);
if(island.size > 1){
this.islands.push(island);
}
}
}
}
for(var i in this.islands){
let _ = BABYLON.MeshBuilder.CreatePlane("quad", {width: 10, height: 10}, this.scene);
_.position.y = this.terrain.getHeightFromMap(this.islands[i].position[0], this.islands[i].position[1]) + 1;
_.position.x = this.islands[i].position[0];
_.position.z = this.islands[i].position[1];
// remove the island from the list
if(this.islands[i].size < 10){
this.islands.splice(i, 1);
}
}
}
Output: (Warning: Lags the browser!)
Expected output: (One island's details) A single 2d coordinate denoting the position of the centre of the island, and the island's area.

Color quantization using euclidean distance gives jumpy results

I'm working on an art project which converts pixels of live video feed into corporate logos based on the distance (in RGB) between the colors of the two. While this functions, it gives a jittery result. Seem like some points in the color space teeter in a sort of superposition between two "closest" points. I'm attempting a sort of naive clustering solution right now because I believe any proper one will be too slow for live video. I'm wondering if anyone has any good ideas to solve this problem? I'll include my code and an example of the result. Thank you!
(imgs array is the logos)
current result: https://gifyu.com/image/fk2y
function distance(r1, g1, b1, bright1, r2, g2, b2, bright2) {
d =
((r2 - r1) * 0.3) ** 2 +
((g2 - g1) * 0.59) ** 2 +
((b2 - b1) * 0.11) ** 2 +
((bright2 - bright1) * 0.75) ** 2;
return Math.round(d);
}
function draw() {
if (x > 100 && z == true) {
video.loadPixels();
for (var y = 0; y < video.height; y++) {
for (var x = 0; x < video.width; x++) {
var index = (video.width - x - 1 + y * video.width) * 4;
var r = video.pixels[index];
var g = video.pixels[index + 1];
var b = video.pixels[index + 2];
var bright = (r + g + b) / 3;
let least = 9999999;
for (var i = 0; i < imgs.length; i++) {
if (
distance(
imgs[i].r,
imgs[i].g,
imgs[i].b,
imgs[i].bright,
r,
g,
b,
bright
) < least
) {
least = distance(
imgs[i].r,
imgs[i].g,
imgs[i].b,
imgs[i].bright,
r,
g,
b,
bright
);
place = imgs[i].img;
}
}
image(place, round(x * vScale), y * vScale, vScale, vScale);
}
}
}
}

Javascript for loop conditional iterations and resetting variable

With the following code, I'm looping through an array of colors (favorites), creating rectangles for a jsPDF document.
After 5 iterations, I want to reset the x variable back to startX and then add 1.875 with each iteration. Likewise for the next 5 iterations: reset x to startX adding 1.875 until 10, then again until 15.
I'm just not having any luck resetting x in these conditionals. I'm sure it's something obvious but what am I missing here?
Or should I structure the loop in a different way?
What I'm trying to accomplish is create up to 3 rows of 5 rectangles. Once I hit 5, start a new row, hence the reset of x which is a page location coordinate.
let startX = 1
let startY = 1
let secondY = 4
let thirdY = 6.5
let n = favorites.length
for (let i = 0, x = startX, y = startY; i < n; x += 1.875, i++) {
if (i < 5) {
doc.setFillColor(favorites[i].h)
doc.rect(x, y, 1.5, 1, 'F')
doc.text(favorites[i].h.toString(), x, y + 1.5)
} else if (i >= 5 && i < 10) {
x = 1 // resets but then doesn't increment
y = secondY
doc.setFillColor(favorites[i].h)
doc.rect(x, y, 1.5, 1, 'F')
doc.text(favorites[i].h.toString(), x, y + 1.5)
} else if (i >= 10 && i < 15) {
x = 1 // resets but then doesn't increment
y = thirdY
doc.setFillColor(favorites[i].h)
doc.rect(x, y, 1.5, 1, 'F')
doc.text(favorites[i].h.toString(), x, y + 1.5)
}
}
You can use the modulo operator (%), and set x and y outside the loop declaration:
const yValues = [1, 4, 6.5];
for (let i = 0 ; i < 15; i++) {
const x = 1 + ((i%5) * 1.875);
const y = yValues[Math.floor(i/5)];
// commented lines to make this example run
// doc.setFillColor(favorites[i].h)
// doc.rect(x, y, 1.5, 1, 'F')
// doc.text(favorites[i].h.toString(), x, y + 1.5)
console.log({x,y});
}
Incrementation in a for loop occur before any commands in the loop. Right now, every iteration in your second and third if blocks resets x to 1, and always does so after x's incrementation in the for loop, thus overwriting it. That's why x isn't changing.
A better approach might be to increment only i, and set x to depend on i's value, something like this:
x = 1 + ((i - 5) * 1.875)
x = 1 + ((i - 10) * 1.875)
And actually, it would be even better to use startX instead of 1:
x = startX + ((i - 5) * 1.875)
x = startX + ((i - 10) * 1.875)

Getting 180 Degree in a Triangle using Canvas Logic

I am trying to create a small program where all degrees in a triangle will add up to 180 degrees. So far what I have is that when I use the Math.Random method, I get a random number but somethings the degrees do not add up to 180°. I have tried using the if condition statement but no luck so far.
Here is my code:
https://jsfiddle.net/14p880p9/
var a = Math.round((Math.random() * 100) + 1);
var b = Math.round((Math.random() * 100) + 1);
var c = Math.round((Math.random() * 100) + 1);
var d = a + b + c;
ctx.beginPath();
ctx.moveTo(50, 10);
ctx.lineTo(50, 450);
ctx.lineTo(650, 400);
ctx.closePath();
ctx.lineWidth = 10;
ctx.strokeStyle = '#666666';
ctx.stroke();
ctx.fillStyle = "#FFCC00";
ctx.fill();
ctx.font = "30px Comic Sans MS";
ctx.fillStyle = "black";
ctx.fillText(a + "°",52,60);
//width distance, and then altitude
ctx.font = "30px Comic Sans MS";
ctx.fillStyle = "black";
ctx.fillText(b+ "°",60,420);
//width distance, and then altitude
ctx.font = "30px Comic Sans MS";
ctx.fillStyle = "black";
ctx.fillText(c + "°",570,400);
Use this, it can generate ANY combination, unlike the limited above option where one of the sides must be 1-100.
var a = Math.round((Math.random() * 90) + 1);
var b = Math.round((Math.random() * 90) + 1);
var c = 180 - a - b;
a) either use a stick...
An easy way to tackle this is to visualize your 180 as a stick. You want to break that stick into 3 pieces. So all you need to do is generate two non-equal rounded random values from 1 to 179 (the cutting points: one & two). And your random values will be:
a: from zero to smallest value cutting point
b: from smallest to largest value cutting points
c: from largest value cutting point to 180
var Triangle = function() {
this.getRandom = function() {
// 1 - 179 random integer generator
return Math.round(Math.random() * (179 - 1) + 1);
}
this.one = this.getRandom();
this.two = this.getRandom();
while (this.one == this.two) {
// if equal, regenerate second - we don't want any segment to have 0 length
this.two = this.getRandom();
}
return {
'a': Math.min(this.one, this.two),
'b': Math.max(this.one, this.two) - Math.min(this.one, this.two),
'c': 180 - Math.max(this.one, this.two)
}
}
// Now let's make a few tests... how about 180 tests?
for (var i = 0; i < 180; i++) {
var t = new Triangle;
var div = document.createElement('div');
div.innerHTML = t.a + ' + ' + t.b + ' + ' + t.c + ' = ' +
(t.a + t.b + t.c);
document.getElementsByTagName('body')[0].appendChild(div);
}
div {
width: 33.3333%;
float: left;
padding: .5rem 0;
text-align: center;
}
b) ... or maths
While the above method is easy to visualize, and ensures each segment value has equal chances at being anywhere between 1 and 178 (total - (parts - 1 )), it's not particularly efficient from a programming point of view.
Each time one of the cutting points overlaps an existing one, it needs to be recalculated. In our case that would be quite rare but, given variable values for total and parts, the odds of it happening might differ.
Besides, we can totally avoid having to regenerate any value, thus saving computing power and, ultimately, the planet, or at least delaying its doom by an insignificant amount of time.
If we look at this from a mathematical point of view, we'll notice
at least 1 part will be smaller than 61 ((180 / (3 - 0)) + 1)
at least 2 parts will be smaller than 91 ((180 / (3 - 1)) + 1)
So, as a general rule, at least n parts will be smaller than (total / (parts - (n - 1)) + 1). Now let's rewrite our method, generating the minimal amount or random numbers, in the correct range of possible values.
We also need to add as a last value the difference between total and the sum of all previous values.
To make it more useful, I also considered total and parts as variables, so the method could be used to segment any total number into any number of parts.
var Segmentation = function (total, parts) {
this.random = function (min, max) {
return Math.round(Math.random() * (max - min) + min);
}
this.segments = [];
for (var i = 0; i < parts - 1; i++) {
this.segments.push(this.random(parts - i, total / parts + 1));
}
this.segments.push(total - this.segments.reduce((a, b) => a + b, 0));
return this.segments;
}
// let's test it
var h1 = document.createElement('h2');
h1.innerHTML = 'Triangles';
document.getElementsByTagName('body')[0].appendChild(h1);
for (var i = 0; i < 21; i++) {
var t = new Segmentation(180, 3),
div = document.createElement('div');
div.innerHTML = '';
for (var j = 0; j < t.length; j++) {
div.innerHTML += t[j] + (t.length - j > 1 ? ' + ' : ' = ');
}
div.innerHTML += t.reduce((a, b) => a + b, 0);
document.getElementsByTagName('body')[0].appendChild(div);
}
var h1 = document.createElement('h2');
h1.innerHTML = '<hr />Rectangles';
document.getElementsByTagName('body')[0].appendChild(h1);
for (var i = 0; i < 21; i++) {
var t = new Segmentation(360, 4),
div = document.createElement('div');
div.innerHTML = '';
for (var j = 0; j < t.length; j++) {
div.innerHTML += t[j] + (t.length - j > 1 ? ' + ' : ' = ');
}
div.innerHTML += t.reduce((a, b) => a + b, 0);
document.getElementsByTagName('body')[0].appendChild(div);
}
var h1 = document.createElement('h2');
h1.innerHTML = '<hr />Random segments';
document.getElementsByTagName('body')[0].appendChild(h1);
for (var i = 0; i < 21; i++) {
var total = Math.round(Math.random() * (2000 - 200) + 200),
parts = Math.round(Math.random() * (8 - 3) + 3),
t = new Segmentation(total, parts);
var div = document.createElement('div');
div.className = ('full');
div.innerHTML = '';
for (var j = 0; j < t.length; j++) {
div.innerHTML += t[j] + (t.length - j > 1 ? ' + ' : ' = ');
}
div.innerHTML += t.reduce((a, b) => a + b, 0);
document.getElementsByTagName('body')[0].appendChild(div);
}
div {
width: 33.3333%;
float: left;
padding: .5rem 0;
text-align: center;
}
div.full {
width: 100%;
text-align: initial;
}
Using this method, the first entry in the array has the biggest chances of having the smallest value while the last part has the biggest chances of having the highest value.
Note: Using this in a production environment is not recommended without sanitizing the input values.
Another note: To calculate the sum of all existing values in an array I used this awesome answer's method.
Your logic for the generation of the randomness is a little off. At the minute you're asking it to generate 3 random numbers between 1-100. The downside to this approach is the numbers have no bearing on having to add to 180;
You should update this logic to know that the total must equal 180. Something like this:
var a = Math.round((Math.random() * 100) + 1); // generates a random number between 1-100
var b = Math.round((Math.random() * (179-a)) + 1); // generates a random number between 1-179 (minus a)
var c = 180 - a - b; // is the remaining value of 180 - a - b
var d = a + b + c;
JSFIDDLE

How to plot the graph based on equation using js

I need to plot a graph in a canvas. But how can I use an algebra equation as input, and based on the equation, draw the curve, using javascript?
For example:
x2+5y=250
The equation plots a graph with both positive and negative values.
<!DOCTYPE html>
<html>
<head>
<title>Interactive Line Graph</title>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.1.min.js"></script>
<script>
var graph;
var xPadding = 30;
var yPadding = 30;
var data = { values:[
{ X: "1", Y: 15 },
{ X: "2", Y: 35 },
{ X: "3", Y: 60 },
{ X: "4", Y: 14 },
{ X: "5", Y: 20 },
{ X: "6", Y: 95 },
]};
// Returns the max Y value in our data list
function getMaxY() {
var max = 0;
for(var i = 0; i < data.values.length; i ++) {
if(data.values[i].Y > max) {
max = data.values[i].Y;
}
}
max += 10 - max % 10;
return max;
}
// Return the x pixel for a graph point
function getXPixel(val) {
return ((graph.width() - xPadding) / data.values.length) * val + (xPadding * 1.5);
}
// Return the y pixel for a graph point
function getYPixel(val) {
return graph.height() - (((graph.height() - yPadding) / getMaxY()) * val) - yPadding;
}
$(document).ready(function() {
graph = $('#graph');
var c = graph[0].getContext('2d');
c.lineWidth = 2;
c.strokeStyle = '#333';
c.font = 'italic 8pt sans-serif';
c.textAlign = "center";
// Draw the axises
c.beginPath();
c.moveTo(xPadding, 0);
c.lineTo(xPadding, graph.height() - yPadding);
c.lineTo(graph.width(), graph.height() - yPadding);
c.stroke();
// Draw the X value texts
for(var i = 0; i < data.values.length; i ++) {
c.fillText(data.values[i].X, getXPixel(i), graph.height() - yPadding + 20);
}
// Draw the Y value texts
c.textAlign = "right"
c.textBaseline = "middle";
for(var i = 0; i < getMaxY(); i += 10) {
c.fillText(i, xPadding - 10, getYPixel(i));
}
c.strokeStyle = '#f00';
// Draw the line graph
c.beginPath();
c.moveTo(getXPixel(0), getYPixel(data.values[0].Y));
for(var i = 1; i < data.values.length; i ++) {
c.lineTo(getXPixel(i), getYPixel(data.values[i].Y));
}
c.stroke();
// Draw the dots
c.fillStyle = '#333';
for(var i = 0; i < data.values.length; i ++) {
c.beginPath();
c.arc(getXPixel(i), getYPixel(data.values[i].Y), 4, 0, Math.PI * 2, true);
c.fill();
}
});
</script>
</head>
<body>
<canvas id="graph" width="200" height="150">
</canvas>
</body>
</html>
[i am add one example ploter in math.js ] i want to how to full screen plot the graph and mouse are cilck in graph any point to show the details in x&y value.so how to change please help me.
Parsing linear equation.
Or maybe it is the Parsing of the equation that the question is about.
This answer shows how to parse a simple linear equation.
User inputs x2+5y=230 and you need to solve and plot for y for f(x) which would be the function function(x) { return (3 * x -230) / -5; }
Will assume the equation is always in the same form with x and y and some scalars and constants scalar * x + const + scalar * y = const
Define the rules
Rules
Only x and y will be considered variables.
A term is a scalar and a variable 2x or a constant +1.
All additional characters will be ignored including *,/,%
Numbers can have decimal places. Valid numbers 1 +1 0.2 -2 10e5
Scalars must be adjacent to variables 3y2 becomes 6y 3y-2 stays as is.
Parsing
To parse a equation we must break it down into unambiguous easy to manipulate units. In this case a unit I call a term and will have 3 properties.
scalar A number
variable the name of the variable x,y or null for constants
side which side of the equation the term is Left or right
An example equation
2x + 2 + 3y = 4x - 1y
First parsed to create
terms
// shorthand not code
{2,x,true; // true is for left
{2,null,true; // null is a constant
{3,y,true;
{4,x,false;
{-1,y,false;
Once all the terms are parsed then the equation is solved by summing all the terms for x, y and constants and moving everything to the left flipping the sign of any values on the right.
sumX = 2 + -4; //as 4x is on the right it becomes negative
sumY = 3 + 1;
const = 2;
Making the equation
-2x + 4y + 2 = 0
Then move the y out to the right and divide the left by its scalar.
-2x + 2 = 4y
(-2x + 2)/-4 = y
The result is a function that we can call from javascript will the value of x and get the value of y.
function(x){ return (-2 * x + 2) / 4; }
The Parser
The following function parses and returns a function for input equation for x. That function then use to plot the points in the demo below.
function parseEquation(input){
// Important that white spaces are removed first
input = input.replace(/\s+/g,""); // remove whitespaces
input = input.replace(/([\-\+])([xy])/g,"$11$2"); // convert -x -y or +x +y to -1x -1y or +1x +1y
// just to make the logic below a little simpler
var newTerm = () => {term = { val : null, scalar : 1, left : left, };} // create a new term
var pushTerm = () => {terms.push(term); term = null;} // push term and null current
// regExp [xy=] gets "x","y", or "="" or [\-\+]??[0-9\.]+ gets +- number with decimal
var reg =/[xy=]|[\-\+]??[0-9\.eE]+/g; // regExp to split the input string into parts
var parts = input.match(reg); // get all the parts of the equation
var terms = []; // an array of all terms parsed
var term = null; // Numbers as constants and variables with scalars are terms
var left = true; // which side of equation a term is
parts.forEach( p=> {
if (p === "x" || p === "y") {
if (term !== null && term.val !== null) { // is the variable defined
pushTerm(); // yes so push to the stack and null
}
if (term === null) { newTerm(); } // do we need a new term?
term.val = p;
} else if( p === "=") { // is it the equals sign
if (!left) { throw new SyntaxError("Unxpected `=` in equation."); }
if (term === null) { throw new SyntaxError("No left hand side of equation."); }// make sure that there is a left side
terms.push(term); // push the last left side term onto the stack
term = null;
left = false; // everything on the right from here on in
} else { // all that is left are numbers (we hope)
if (isNaN(p)){ throw new SyntaxError("Unknown value '"+p+"' in equation"); }//check that there is a number
if (term !== null && (p[0] === "+" || p[0] === "-")) { // check if number is a new term
pushTerm(); // yes so push to the stack and null
}
if (term === null) { newTerm(); } // do we need a new term?
term.scalar *= Number(p); // set the scalar to the new value
}
});
if (term !== null) { // there may or may not be a term left to push to the stack
pushTerm();
}
// now simplify the equation getting the scalar for left and right sides . x on left y on right
var scalarX = 0;
var scalarY = 0
var valC = 0; // any constants
terms.forEach(t => {
t.scalar *= !t.left ? -1 : 1; // everything on right is negative
if (t.val === "y") {
scalarY += -t.scalar; // reverse sign
} else if (t.val === "x") {
scalarX += t.scalar;
} else {
valC += t.scalar;
}
})
// now build the code string for the equation to solve for x and return y
var code = "return (" + scalarX + " * x + (" + valC + ")) / "+scalarY +";\n";
var equation = new Function("x",code); // create the function
return equation;
}
The following usage examples are all the same equation
var equation = parseEquation("x2+5y+x=230");
var y = equation(10); // get y for x = 10;
equation = parseEquation("x2+x=230-5y");
equation = parseEquation("x2+x-30=200-2y-3y");
equation = parseEquation("200- 2y-3y = x2+x-30");
equation = parseEquation("200-2y- 3y - x2-x+30=0");
equation = parseEquation("100.0 + 100-2y- 3y - x2-x+30=0");
equation = parseEquation("1e2 + 10E1-2y- 3y - x2-x+30=0");
Demo
I have added it to the code in the answer markE has already given. (hope you don't mind markE)
function plot(equation) {
var graph;
var xPadding = 30;
var yPadding = 30;
var data = {
values : [{
X : "1",
Y : 15
}, {
X : "2",
Y : 35
}, {
X : "3",
Y : 60
}, {
X : "4",
Y : 14
}, {
X : "5",
Y : 20
}, {
X : "6",
Y : -30
},
]
};
// Returns the max Y value in our data list
function getMaxY() {
var max = 0;
for (var i = 0; i < data.values.length; i++) {
if (data.values[i].Y > max) {
max = data.values[i].Y;
}
}
max += 10 - max % 10;
return max;
}
var scaleA = 1.4;
// Return the x pixel for a graph point
function getXPixel(val) {
return ((graph.width() / scaleA - xPadding) / data.values.length) * val + (xPadding * 1.5);
}
// Return the y pixel for a graph point
function getYPixel(val) {
return graph.height() / scaleA - (((graph.height() / scaleA - yPadding) / getMaxY()) * val) - yPadding;
}
graph = $('#graph');
var c = graph[0].getContext('2d');
c.clearRect(0,0,graph[0].width,graph[0].height);
c.lineWidth = 2;
c.strokeStyle = '#333';
c.font = 'italic 8pt sans-serif';
c.textAlign = "center";
// Draw the axises
c.beginPath();
c.moveTo(xPadding, 0);
c.lineTo(xPadding, graph.height() / scaleA - yPadding);
c.lineTo(graph.width(), graph.height() / scaleA - yPadding);
c.stroke();
// Draw the X value texts
for (var i = 0; i < data.values.length; i++) {
c.fillText(data.values[i].X, getXPixel(i), graph.height() / scaleA - yPadding + 20);
}
// Draw the Y value texts
c.textAlign = "right"
c.textBaseline = "middle";
for (var i = 0; i < getMaxY(); i += 10) {
c.fillText(i, xPadding - 10, getYPixel(i));
}
c.strokeStyle = '#f00';
// Draw the line graph
c.beginPath();
c.moveTo(getXPixel(0), getYPixel(equation(0)));
for (var i = 1; i < data.values.length; i++) {
c.lineTo(getXPixel(i), getYPixel(equation(i)));
}
c.stroke();
// Draw the dots
c.fillStyle = '#333';
for (var i = 0; i < data.values.length; i++) {
c.beginPath();
c.arc(getXPixel(i), getYPixel(equation(i)), 4, 0, Math.PI * 2, true);
c.fill();
}
}
var codeText = "";
function parseEquation(input){
// Important that white spaces are removed first
input = input.replace(/\s+/g,""); // remove whitespaces
input = input.replace(/([\-\+])([xy])/g,"$11$2"); // convert -x -y or +x +y to -1x -1y or +1x +1y
// just to make the logic below a little simpler
var newTerm = () => {term = { val : null, scalar : 1, left : left, };} // create a new term
var pushTerm = () => {terms.push(term); term = null;} // push term and null current
// regExp [xy=] gets "x","y", or "="" or [\-\+]??[0-9\.]+ gets +- number with decimal
var reg =/[xy=]|[\-\+]??[0-9\.eE]+/g; // regExp to split the input string into parts
var parts = input.match(reg); // get all the parts of the equation
var terms = []; // an array of all terms parsed
var term = null; // Numbers as constants and variables with scalars are terms
var left = true; // which side of equation a term is
parts.forEach(p=>{
if (p === "x" || p === "y") {
if (term !== null && term.val !== null) { // is the variable defined
pushTerm(); // yes so push to the stack and null
}
if (term === null) { newTerm(); } // do we need a new term?
term.val = p;
} else if( p === "="){ // is it the equals sign
if (!left) { throw new SyntaxError("Unxpected `=` in equation."); }
if (term === null) { throw new SyntaxError("No left hand side of equation."); }// make sure that there is a left side
terms.push(term); // push the last left side term onto the stack
term = null;
left = false; // everything on the right from here on in
} else { // all that is left are numbers (we hope)
if (isNaN(p)){ throw new SyntaxError("Unknown value '"+p+"' in equation"); }//check that there is a number
if (term !== null && (p[0] === "+" || p[0] === "-")){ // check if number is a new term
pushTerm(); // yes so push to the stack and null
}
if(term === null){ newTerm(); } // do we need a new term?
term.scalar *= Number(p); // set the scalar to the new value
}
});
if(term !== null){// there may or may not be a term left to push to the stack
pushTerm();
}
// now simplify the equation getting the scalar for left and right sides . x on left y on right
var scalarX = 0;
var scalarY = 0
var valC = 0; // any constants
terms.forEach(t => {
t.scalar *= !t.left ? -1 : 1; // everything on right is negative
if (t.val === "y") {
scalarY += -t.scalar; // reverse sign
} else if (t.val === "x") {
scalarX += t.scalar;
} else {
valC += t.scalar;
}
})
// now build the code string for the equation to solve for x and return y
var code = "return (" + scalarX + " * x + (" + valC + ")) / "+scalarY +";\n";
codeText = code;
var equation = new Function("x",code); // create the function
return equation;
}
function parseAndPlot(){
var input = eqInput.value;
try{
var equation = parseEquation(input);
plot(equation);
error.textContent ="Plot of "+input+ " as 'function(x){ "+codeText+"}'";
}catch(e){
error.textContent = "Error parsing equation. " + e.message;
}
}
var button = document.getElementById("plot");
var eqInput = document.getElementById("equation-text");
var error = document.getElementById("status");
button.addEventListener("click",parseAndPlot);
parseAndPlot();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<canvas id="graph" width="200" height="150"></canvas> <br>
Enter a linear equation : <input id="equation-text" value="x2 + 5y = 250" type="text"></input><input id="plot" value="plot" type=button></input><div id="status"></div>
I think I understand what you're asking...
Your existing code automatically puts your y-axis at the bottom of the canvas so negative y-values will be off-canvas.
Quick solution
The quickest solution is to divide graph.height()/2 so that your graph has it's y-axis near center-canvas. This leaves room for negative values.
Better solution
The better solution is to redesign your graphing system to allow for solutions in all axis directions.
Refactored code showing the quick solution:
I leave it to you to extend the y-axis labels in the negative direction (if desired)
var graph;
var xPadding = 30;
var yPadding = 30;
var data = { values:[
{ X: "1", Y: 15 },
{ X: "2", Y: 35 },
{ X: "3", Y: 60 },
{ X: "4", Y: 14 },
{ X: "5", Y: 20 },
{ X: "6", Y: -30 },
]};
// Returns the max Y value in our data list
function getMaxY() {
var max = 0;
for(var i = 0; i < data.values.length; i ++) {
if(data.values[i].Y > max) {
max = data.values[i].Y;
}
}
max += 10 - max % 10;
return max;
}
// Return the x pixel for a graph point
function getXPixel(val) {
return ((graph.width()/2 - xPadding) / data.values.length) * val + (xPadding * 1.5);
}
// Return the y pixel for a graph point
function getYPixel(val) {
return graph.height()/2 - (((graph.height()/2 - yPadding) / getMaxY()) * val) - yPadding;
}
graph = $('#graph');
var c = graph[0].getContext('2d');
c.lineWidth = 2;
c.strokeStyle = '#333';
c.font = 'italic 8pt sans-serif';
c.textAlign = "center";
// Draw the axises
c.beginPath();
c.moveTo(xPadding, 0);
c.lineTo(xPadding, graph.height()/2 - yPadding);
c.lineTo(graph.width(), graph.height()/2 - yPadding);
c.stroke();
// Draw the X value texts
for(var i = 0; i < data.values.length; i ++) {
c.fillText(data.values[i].X, getXPixel(i), graph.height()/2 - yPadding + 20);
}
// Draw the Y value texts
c.textAlign = "right"
c.textBaseline = "middle";
for(var i = 0; i < getMaxY(); i += 10) {
c.fillText(i, xPadding - 10, getYPixel(i));
}
c.strokeStyle = '#f00';
// Draw the line graph
c.beginPath();
c.moveTo(getXPixel(0), getYPixel(data.values[0].Y));
for(var i = 1; i < data.values.length; i ++) {
c.lineTo(getXPixel(i), getYPixel(data.values[i].Y));
}
c.stroke();
// Draw the dots
c.fillStyle = '#333';
for(var i = 0; i < data.values.length; i ++) {
c.beginPath();
c.arc(getXPixel(i), getYPixel(data.values[i].Y), 4, 0, Math.PI * 2, true);
c.fill();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<canvas id="graph" width="200" height="300"></canvas>

Categories