Traverse a Hexagon - javascript

I can easily traverse the following from left to right, but I'm having a lot of trouble (2 days in, and no progress) getting a formula to traverse it from top right & bottom right.
Basically, I'm looking for a formula which can retrieve the following values:
let topRight = [
[ h[2][4], h[3][3], h[4][2] ],
[ h[1][3], h[2][3], h[3][2], h[4][1] ],
[ h[0][2], h[1][2], h[2][2], h[3][1], h[4][0] ],
[ h[0][1], h[1][1], h[2][1], h[3][0] ],
[ h[0][0], h[1][0], h[2][0] ]
]
let bottomRight = [
[ h[2][4], h[1][3], h[0][2] ],
[ h[3][3], h[2][3], h[1][2], h[0][1] ],
[ h[4][2], h[3][2], h[2][2], h[1][1], h[0][0] ],
[ h[4][1], h[3][1], h[2][1], h[1][0] ],
[ h[4][0], h[3][0], h[2][0] ]
]
The only part that I could get working was the topRight x value:
function hexagonArraySize(row) {
if (row < this.size) {
return (this.size + row)
} else {
return (this.size * 2 - (row % this.size) - 2)
}
}
for (var i = 0, l = this.size * 2 - 1, j = l % size; i < l; ++i, ++j) {
this.h[j] = new Array(this.hexagonArraySize(i)).fill().map((_, b) => {
let x = (i > Math.floor(this.size / 2)) ? b : b + (this.size - i - 1)
let y = 0 // didn't found the formula for this one...
}, []).join("")
}
I made a fiddle available here: https://jsfiddle.net/0qwf8m1p/12/
There still is topRight y, bottomRight x & y to be found.

Ok, so you worked out topRight x coordinate.
Both topRight and bottomRight y coordinates have the same equation:
You start with (size - 1) * 2
First value always equals (size - 1) * 2 - i) (where i is the sequence index)
Then you repeat first value i times, but at most size times
This wraps up to the following formula:
let y = (size - 1) * 2 - i - Math.max(0, b - Math.min(i, size - 1))
| | |
| first value | repeat at most size times |
Next, you have to calculate x value for bottomRight. You have two cases here:
If i is less than size / 2 then value equals sequence length - b (item index)
Otherwise value equals (size - 1) * 2 - b
This wraps up to the following formula:
let x = (i > Math.floor(size / 2)) ? (size - 1) * 2 - b : hexagonArraySize(i) - b - 1
| | |
| check index | second half | first half
Working fiddle here

Here's a possible implementation for the two methods. They work by initializing the x,y coordinates of the start for each row in the transformed hexagon, then conditionally iterate the x value based on whether or not the y value passed the size of the hexagon, where the indices "bend":
function hexagon (size) {
const height = size * 2 - 1;
return Array.from({ length: height }, (_, y) => {
const width = size + Math.min(y, height - y - 1);
return Array.from({ length: width }, (_, x) => [y, x]);
})
}
const h = hexagon(3);
function topRight (h) {
const height = h.length;
const size = (height + 1) / 2;
const t = [];
for (let i = height; i > 0; i--) {
const width = size + Math.min(i - 1, height - i);
const row = Array.from({ length: width });
let y = Math.max(i - size, 0);
let x = i - 1;
for (let j = 0; j < width; j++) {
row[j] = h[y++][y >= size ? x-- : x];
}
t.push(row);
}
return t;
}
function bottomRight (h) {
const height = h.length;
const size = (height + 1) / 2;
const t = [];
for (let i = 0; i < height; i++) {
const width = size + Math.min(i, height - i - 1);
const row = Array.from({ length: width });
let y = height - Math.max(size - i, 1);
let x = height - i - 1;
for (let j = 0; j < width; j++) {
row[j] = h[y][y-- < size ? x-- : x];
}
t.push(row);
}
return t;
}
console.log('topRight');
topRight(h).forEach(row => console.log(JSON.stringify(row)));
console.log('bottomRight');
bottomRight(h).forEach(row => console.log(JSON.stringify(row)));
If you'd like a more object-oriented approach, here's a possible alternative:
class Hexagon extends Array {
constructor (size, map = (row, column) => [row, column]) {
const length = size * 2 - 1;
super(length);
this.fill();
this.size = size;
this.forEach((_, row, hexagon) => {
const width = size + Math.min(row, length - row - 1);
hexagon[row] = Array.from({ length: width }, (_, column) => map(row, column));
});
}
topRight () {
const { length, size } = this;
return new Hexagon(size, (row, column) => {
const upper = Math.max(row * 2 - length, 0) + 1;
const y = Math.max(size - 1 - row, 0) + column;
const x = Math.max(Math.min(length - 1 - row, length - upper - column), 0);
return this[y][x];
});
}
bottomRight () {
const { length, size } = this;
return new Hexagon(size, (row, column) => {
const upper = Math.max(row * 2 - length, 0) + 1;
const y = Math.min(size + row, length) - 1 - column;
const x = Math.max(Math.min(length - 1 - row, length - upper - column), 0);
return this[y][x];
});
}
}
let h = new Hexagon(3);
console.log('topRight');
h.topRight().forEach(row => console.log(JSON.stringify(row)));
console.log('bottomRight');
h.bottomRight().forEach(row => console.log(JSON.stringify(row)));

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.

D3. Create an Image Cloud with D3

Im trying to create an Image Cloud component like the one in the image.
I've tried using a simple d3 alorithm with rects and force to center the images and to spread them with collisions. The problem is that it looks like this:
The images look too far apart one of each other, and even with lower collition radius the cloud doesn't use all avaliable space (fit smaller images between gaps). Is it there a way to make this component with the layouts given by d3? should I modify some other layout? I know I can implement an square collition force, but the results doesnt look as the one desired.
Any help is appreciated. Thank you very much.
EDIT: Hello again. I finally made it look somewhat similar to the desired result.
Sadly, now I have more problems. The first and most important one: the big images should be close one of each other and should be as centered as possible.
The second one: Im using React, and my data comes from calling the exported render function from outside the file (see first code snippet), the problem is that when I update the data, the image just dissapears and it doesn't adjust the layout again. How can i make it update it? A temporary hack that I made is to remove every image and rect from the DOM before rendering.
Again, thank you for your time.
Edit: I managed to get everything like I wanted. There is still room for improvement but this is functional for now. I managed to get an observer for the div and a somewhat good state management with React. Someday I will upload the react component to github
// specify svg width and height;
const width = 500, height = 500;
const listenTo = Math.min(width, height);
// create svg and g DOM elements;
let svg = d3.select('body')
.append('svg')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.attr('width', listenTo)
.attr('height', listenTo)
.append('g')
// move 0,0 to the center
.attr('transform', `translate(${width >>1}, ${height>>1})`);
var images = [], maxImages = 100, maxWeight = 50, minWeight = 1, padding=3;
for(let i = 0; i< maxImages -1; i++){
const weight = (Math.random() *(maxWeight - minWeight)) + minWeight;
images.push({
url:`https://via.placeholder.com/100?text=${Math.ceil(weight)}`,
weight
})
}
// make one image with a weight 3 times bigger for visualization testing propouses
images.push({
url: `https://via.placeholder.com/100?text=${maxWeight * 3}`,
weight: maxWeight * 3,
fx: 0,
fy: 0
})
images.sort((a, b) => b.weight - a.weight);
// make it so the biggest images is equal to 10% of canvas, and thre smallest one 1%
const scl = ((100 / maxImages) / 100);
console.log(scl);
const maxImageSize = listenTo * 0.1;
const minImageSize = listenTo * scl;
// function to scale the images
const scaleSize = d3.scaleLinear().domain([minWeight, maxWeight*3]).range([minImageSize, maxImageSize]).clamp(true);
// append the rects
let vizImages = svg.selectAll('.image-cloud-image')
.data(images)
.enter()
.append('svg:image')
.attr('class', '.image-cloud-image')
.attr('height', d => scaleSize(d.weight))
.attr('width', d => scaleSize(d.weight))
.attr('id', d => d.url)
.attr('xlink:href', d => d.url);
vizImages.exit().remove();
// create the collection of forces
const simulation = d3.forceSimulation()
// set the nodes for the simulation to be our images
.nodes(images)
// set the function that will update the view on each 'tick'
.on('tick', ticked)
.force('center', d3.forceCenter())
.force('cramp', d3.forceManyBody().strength(listenTo / 100))
// collition force for rects
.force('collide', rectCollide().size(d=> {
const s = scaleSize(d.weight);
return [s + padding, s + padding];
}));
// update the position to new x and y
function ticked() {
vizImages.attr('x', d => d.x).attr('y', d=> d.y);
}
// Rect collition algorithm. i don't know exactly how it works
// https://bl.ocks.org/cmgiven/547658968d365bcc324f3e62e175709b
function rectCollide() {
var nodes, sizes, masses
var size = constant([0, 0])
var strength = 1
var iterations = 1
function force() {
var node, size, mass, xi, yi
var i = -1
while (++i < iterations) { iterate() }
function iterate() {
var j = -1
var tree = d3.quadtree(nodes, xCenter, yCenter).visitAfter(prepare)
while (++j < nodes.length) {
node = nodes[j]
size = sizes[j]
mass = masses[j]
xi = xCenter(node)
yi = yCenter(node)
tree.visit(apply)
}
}
function apply(quad, x0, y0, x1, y1) {
var data = quad.data
var xSize = (size[0] + quad.size[0]) / 2
var ySize = (size[1] + quad.size[1]) / 2
if (data) {
if (data.index <= node.index) { return }
var x = xi - xCenter(data)
var y = yi - yCenter(data)
var xd = Math.abs(x) - xSize
var yd = Math.abs(y) - ySize
if (xd < 0 && yd < 0) {
var l = Math.sqrt(x * x + y * y)
var m = masses[data.index] / (mass + masses[data.index])
if (Math.abs(xd) < Math.abs(yd)) {
node.vx -= (x *= xd / l * strength) * m
data.vx += x * (1 - m)
} else {
node.vy -= (y *= yd / l * strength) * m
data.vy += y * (1 - m)
}
}
}
return x0 > xi + xSize || y0 > yi + ySize ||
x1 < xi - xSize || y1 < yi - ySize
}
function prepare(quad) {
if (quad.data) {
quad.size = sizes[quad.data.index]
} else {
quad.size = [0, 0]
var i = -1
while (++i < 4) {
if (quad[i] && quad[i].size) {
quad.size[0] = Math.max(quad.size[0], quad[i].size[0])
quad.size[1] = Math.max(quad.size[1], quad[i].size[1])
}
}
}
}
}
function xCenter(d) { return d.x + d.vx + sizes[d.index][0] / 2 }
function yCenter(d) { return d.y + d.vy + sizes[d.index][1] / 2 }
force.initialize = function (_) {
sizes = (nodes = _).map(size)
masses = sizes.map(function (d) { return d[0] * d[1] })
}
force.size = function (_) {
return (arguments.length
? (size = typeof _ === 'function' ? _ : constant(_), force)
: size)
}
force.strength = function (_) {
return (arguments.length ? (strength = +_, force) : strength)
}
force.iterations = function (_) {
return (arguments.length ? (iterations = +_, force) : iterations)
}
return force
}
function constant(_) {
return function () { return _ }
}
Here's a fiddle for future people

Find randomly placed element (x,y) inside a grid

Given a grid of size 1000, find the x and y coords of a randomly placed element.
I've tried subdividing the grid into four sections, but I also have to make the solution time-complexity efficient.
const GRID_SIZE = 1000
class RandomElement {
constructor() {
const element = {
x: Math.floor(Math.random() * GRID_SIZE),
y: Math.floor(Math.random() * GRID_SIZE)
}
this._element = element
}
findInArea(x1, y1, x2, y2) {
console.log(`Scanning area (${x1}, ${y1}, ${x2}, ${y2})`)
return (
this._element.x >= x1 &&
this._element.y >= y1 &&
this._element.x < x2 &&
this._element.y < y2
)
}
findInCell(x, y) {
console.log(`Scanning cell (${x}, ${y}`)
return this._element.x === x && this._element.y === y
}
}
const RandomElement = new RandomElement()
const iselementHere1 = RandomElement.findInArea(0, GRID_SIZE, 0, GRID_SIZE)
console.log('Is element Here?', iselementHere1)
const iselementHere2 = RandomElement.findInArea(0, GRID_SIZE / 2, GRID_SIZE / 2, GRID_SIZE)
console.log('Is element Here?', iselementHere2)
const iselementHere3 = RandomElement.findInArea(GRID_SIZE / 2, 0, GRID_SIZE, GRID_SIZE / 2)
console.log('Is element Here?', iselementHere3)
const iselementHere4 = RandomElement.findInArea(GRID_SIZE / 2, GRID_SIZE / 2, GRID_SIZE, GRID_SIZE)
console.log('Is element Here?', iselementHere4)
Expressions
xx = Math.floor(this._element.x / (GRID_SIZE / 2))
yy = Math.floor(this._element.y / (GRID_SIZE / 2))
give you cell coordinates in 2x2 grid.
You can combine both in single parameter
cellYX = xx + 2 * yy
to get result 0..3 as cell number
0 | 1
-------
2 | 3
Convert your 2d array into another 2d array where every index holds {value, x, y}. Now sort it row / column wise. Then every element search will take rlogc (sorted column-wise) / clogr (sorted row-wise) and just output the (x,y) of that index (it's not the actual co-ordinates, but the co-ordinates of the given 2d-array)

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>

jQuery animations with a MxN matrix

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)

Categories