I want to draw exp(x) for x in [-5, 5] on a 800x800px canvas. I managed to map [-5, 5] to [0, 800] on the horizontal axis, but I'm struggling to map [exp(-5), exp(5)] to [800, 0] on the vertical axis.
Any idea on how to do that? Thanks in advance.
Here is a detailed example of a way to do it. i and j are canvas coordinates white x and y are the function coordinates.
Basically, mapping from y to j is similar to the inverse of mapping from x to i. But you have to subtract the result from the height since the y coordinate is reversed when drawing on a canvas.
var c = document.querySelector('canvas');
var ctx = c.getContext('2d');
ctx.beginPath();
const width = 400;
const height = 150;
// The bounds
const minx = -5;
const maxx = 5;
const miny = Math.exp(-5);
const maxy = Math.exp(5);
ctx.moveTo(0, height);
for (let i = 0; i < width; i += width / 100) {
// Get x from canvas coordinates
const x = i / width * (maxx - minx) + minx;
const y = Math.exp(x);
// Transform y to canvas coordinates
const j = height - (y - miny) / (maxy - miny) * height;
// Draw in canvas coordinates
ctx.lineTo(i, j);
}
ctx.stroke();
canvas {
border: 1px solid black;
}
<canvas width=400 height=150></canvas>
Related
I'm trying to do an isometric projection in HTML5 and the closest I can get is by scaling and rotating the viewport
window.addEventListener('DOMContentLoaded', (event) => {
class Point {
constructor() {
this.x = 0;
this.y = 0;
}
}
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function tick(time) {
ctx.save();
ctx.translate(250,0);
ctx.scale(1, 0.5);
ctx.rotate(45 * Math.PI /180);
let p = new Point();
p.x = 4*32;
p.y = 2*32;
ctx.fillStyle = 'green';
ctx.fillRect(p.x,p.y,32,32);
for (var x = 0; x < 10; ++x) {
for (var y = 0; y < 10; ++y) {
ctx.strokeStyle = 'black';
ctx.strokeRect(x*32, y*32, 32, 32);
}
}
ctx.restore();
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
});
And it does work..but this isn't a practical solution because then when I try to draw isometric sprites within that scaled and rotated view, it looks really distorted. So I'd rather not go that route unless I can get it to work without distorting my sprites.
I also tried this website https://gamedevelopment.tutsplus.com/tutorials/creating-isometric-worlds-primer-for-game-developers-updated--cms-28392
Which provided an equation for generating isometric grids. I used their cartesianToIsometric function to try and make an iso grid
window.addEventListener('DOMContentLoaded', (event) => {
class Point {
constructor() {
this.x = 0;
this.y = 0;
}
}
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function cartesianToIsometric(cartPt) {
var tempPt = new Point();
tempPt.x = cartPt.x - cartPt.y;
tempPt.y = (cartPt.x + cartPt.y) / 2;
return (tempPt);
}
function tick(time) {
for (var x = 0; x < 50; ++x) {
for (var y = 0; y < 50; ++y) {
let p = new Point();
p.x = x * 32;
p.y = y * 32;
let iso = cartesianToIsometric(p);
ctx.strokeStyle = 'black';
ctx.strokeRect(iso.x, iso.y, 32, 32);
}
}
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
});
but it still looked wrong Unless I rotate or scale the viewport to correct it.
So my question is..how can I draw an isometric grid without scaling and rotating my viewport(if possible)
If I have to scale my viewport like the first example, how can I do it without distorting my sprites.... Sorry if this question is confusing to read my knowledge on this is iffy..
Here's my html file
index.html
<html>
<head>
<script src="index.js"></script>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
Looks like you are so close to the solution but a little bit confused. Let me explain:
Normally, you don't have to rotate and scale your canvas viewport. This is something you do when you create your isometric sprites. You should already have some isometric sprites and all you have to do just to put them in correct isometric coordinates.
Your second approach will do exactly what I mean, calculating the isometric points by using cartesian coordinates. It should work, no need to draw rectangles just place the images on the isometric coordinates.
The only trick here you should place your isometric sprites from their bottom center points:
for (var x = 0; x < 50; ++x) {
for (var y = 0; y < 50; ++y) {
let p = new Point();
p.x = x * 32;
p.y = y * 32;
let iso = cartesianToIsometric(p);
// Apply offset to place each isometric image from its bottom center.
// The default pivot point (top left) won't do good
// because we need to stack them up according to their heights.
let offset = {x: floorImageWidth/2, y: floorImageHeight}
ctx.drawImage(floor, iso.x - offset.x, iso.y - offset.y);
}
}
I also suggest using a separate function to draw iso points (not the rectangles) so that you can debug your positions:
let debug = true;
function debugDraw(time) {
for (var x = 0; x < 50; ++x) {
for (var y = 0; y < 50; ++y) {
let p = new Point();
p.x = x * tileWidth;
p.y = y * tileWidth;
let iso = cartesianToIsometric(p);
// draw pivot point for each tile
ctx.fillStyle = 'yellow';
ctx.fillRect(iso.x - 5, iso.y - 5, 10, 10);
}
}
}
if(debug){
requestAnimationFrame(debugDraw)
}
Plus, here is a working demo for you to experiment further:
https://codepen.io/justintc/pen/eYpMabx
Hope it helps, cheers!
I'm trying to create a little circular "equalizer" effect using JavaScript and HTML canvas for a little project I'm working on, and it works great, except one little thing. It's just a series of rectangular bars moving in time to an mp3 - nothing overly fancy, but at the moment all the bars point in one direction (i.e. 0 radians, or 90 degrees).
I want each respective rectangle around the edge of the circle to point directly away from the center point, rather than to the right. I have 360 bars, so naturally, each one should be 1 degree more rotated than the previous.
I thought that doing angle = i*Math.PI/180 would fix that, but it doesn't seem to matter what I do with the rotate function - they always end up pointing in weird and wonderful directions, and being translated a million miles from where they were. And I can't see why. Can anyone see where I'm going wrong?
My frame code, for reference, is as follows:
function frames() {
// Clear the canvas and get the mp3 array
window.webkitRequestAnimationFrame(frames);
musicArray = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(musicArray);
ctx.clearRect(0, 0, canvas.width, canvas.height);
bars = 360;
for (var i = 0; i < bars; i++) {
// Find the rectangle's position on circle edge
distance = 100;
var angle = i * ((Math.PI * 2) / bars);
var x = Math.cos(angle) * distance + (canvas.width / 2);
var y = Math.sin(angle) * distance + (canvas.height / 2);
barWidth = 5;
barHeight = (musicArray[i] / 4);
// Fill with a blue-green gradient
var grd = ctx.createLinearGradient(x, 0, x + 40, 0);
grd.addColorStop(0, "#00CCFF");
grd.addColorStop(1, "#00FF7F");
ctx.fillStyle = grd;
// Rotate the rectangle according to position
// ctx.rotate(i*Math.PI/180); - DOESN'T WORK
// Draw the rectangle
ctx.fillRect(x, y, barHeight, barWidth);
}
For clarity I've removed part of your code. I'm using rotate as you intended. Also I'm using barHeight = (Math.random()* 50); instead your (musicArray[i]/4); because I wanted to have something to show.
Also I've changed your bars to 180. It's very probable that you won't have 360 bars but 32 or 64 or 128 or 256 . . . Now you can change the numbers of bare to one of these numbers to see the result.
I'm drawing everything around the origin of the canvas and translating the context in the center.
I hope it helps.
const canvas = document.getElementById("c");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 400;
let ch = canvas.height = 400;
let bars = 180;
let r = 100;
ctx.translate(cw / 2, ch / 2)
for (var i = 0; i < 360; i += (360 / bars)) {
// Find the rectangle's position on circle edge
var angle = i * ((Math.PI * 2) / bars);
//var x = Math.cos(angle)*r+(canvas.width/2);
//var y = Math.sin(angle)*r+(canvas.height/2);
barWidth = 2 * Math.PI * r / bars;
barHeight = (Math.random() * 50);
ctx.fillStyle = "green";
// Rotate the rectangle according to position
// ctx.rotate(i*Math.PI/180); - DOESN'T WORK
// Draw the rectangle
ctx.save();
ctx.rotate(i * Math.PI / 180);
ctx.fillRect(r, -barWidth / 2, barHeight, barWidth);
//ctx.fillRect(r ,0, barHeight, barWidth);
ctx.restore();
}
canvas {
border: 1px solid
}
<canvas id="c"></canvas>
Here is another solution, I'm preserving your initial trigonometry approach.
But instead of rectangles I used lines, I don't think it makes a difference for you, if what you need is bars moving in time to an mp3 all you need to do is change the var v = Math.random() + 1; to a reading from the Amplitude, and those bars will be dancing.
const canvas = document.getElementById("c");
canvas.width = canvas.height = 170;
const ctx = canvas.getContext("2d");
ctx.translate(canvas.width / 2, canvas.height / 2)
ctx.lineWidth = 2;
let r = 40;
let bars = 180;
function draw() {
ctx.clearRect(-100, -100, 200, 200)
for (var i = 0; i < 360; i += (360 / bars)) {
var angle = i * ((Math.PI * 2) / bars);
var x = Math.cos(angle) * r;
var y = Math.sin(angle) * r;
ctx.beginPath();
var v = Math.random() + 1;
ctx.moveTo(x, y);
ctx.lineTo(x * v, y * v)
grd = ctx.createLinearGradient(x, y, x*2, y*2);
grd.addColorStop(0, "blue");
grd.addColorStop(1, "red");
ctx.strokeStyle = grd;
ctx.stroke();
}
}
setInterval(draw, 100)
<canvas id="c"></canvas>
I have been able to draw the sin wave in horizontal direction as shows the image(image link: https://i.stack.imgur.com/RTpDY.png) and in the vertical direction.
now I need to draw it in an angled 45° direction
could any one help me pleese!
the script code:
var c =document.getElementById("c");
var ctx=c.getContext('2d');
var x=0,y=250,vx=0.05,vy=0.05,a=1;
for(var i=0; i<501;i++){
x += a;
y = Math.floor(500 * (0.5 - 0.15 * Math.sin(vy)));
vy += vx;
// this.ctx.clearRect(0, 0, 500,500);
this.ctx.beginPath();
this.ctx.arc(x, y, 2, 0, Math.PI * 2, true);
this.ctx.closePath();
this.ctx.fillStyle = 'red';
this.ctx.fill();
console.log("x:"+x+"y:"+y+"vy:"+vy);
}
Draw a sin wave along a line
The following will draw a sin wave aligned to a line. The line can be in any direction.
The standard settings
The wave length will be in pixels. For a sin wave to make a complete cycle you need to rotate its input angle by Math.PI * 2 so you will need to convert it to value matching pixel wavelength.
const waveLen = 400; // pixels
The phase of the sin wave is at what part of the wave it starts at, as the wave length is in pixels, the phase is also in pixels, and represents the distance along the wave that denotes the starting angle.
const phase = 200; // mid way
The amplitude of the wave is how far above and below the center line the wave max and min points are. This is again in pixels
const amplitude = 100;
A wave also has an offset, though in this case not really important I will added it as well. In pixels as well
const offset = 0;
The line that marks the center of the wave has a start and end coordinate
const x1 = 20;
const y1 = 20;
const x2 = 400;
const y2 = 400;
And some context settings
const lineWidth = 3;
const lineCap = "round";
const lineJoin = "round";
const strokeStyle = "blue";
Example code
And so to the code that does the rendering, I have expanded the code with comments so you can read what is going on. Below that is a more useable version.
const ctx = canvas.getContext("2d");
canvas.width = innerWidth;
canvas.height = innerHeight;
window.addEventListener("resize", () => {
canvas.width = innerWidth;
canvas.height = innerHeight;
y2 = x2 = innerWidth; // at 45 deg
drawSinWave();
})
const waveLen = 120; // pixels
const phase = 50; // mid way
const amplitude = 25;
const offset = 0;
const x1 = 20;
const y1 = 20;
var x2 = 400; // as vars to let it change to fit resize
var y2 = 400;
function drawSinWave() {
ctx.lineWidth = 3;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.strokeStyle = "blue";
// get the vector form of the line
const vx = x2 - x1;
const vy = y2 - y1;
// Get the length of the line in pixels
const dist = Math.sqrt(vx * vx + vy * vy);
// Make the vector one pixel long to move along the line
const px = vx / dist;
const py = vy / dist;
// We also need a vector to move out from the line (at 90 deg to the ine)
// So rotate the pixel vector 90deg CW
const ax = -py; // a for amplitude vector
const ay = px;
// Begin the path
ctx.beginPath();
// Now loop along every pixel in the line
// We go past the end a bit as floating point errors can cause it to end
// a pixels too early
for (var i = 0; i <= dist + 0.5; i++) {
// fix i if past end
if (i > dist) {
i = dist
} // Carefull dont mess with this ot it will block the page
// Use the distance to get the current angle of the wave
// based on the wave length and phase
const ang = ((i + phase) / waveLen) * Math.PI * 2;
// and at this position get sin
const val = Math.sin(ang);
// Scale to match the amplitude and move to offset
// as the distance from the center of the line
const amp = val * amplitude + offset;
// Get line ceneter at distance i using the pixel vector
var x = x1 + px * i;
var y = y1 + py * i;
// Use the amp vector to move away from the line at 90 degree
x += ax * amp;
y += ay * amp;
// Now add the point
ctx.lineTo(x, y);
}
ctx.stroke();
}
drawSinWave();
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id=canvas width=4 00 height=4 00></canvas>
As a more useable function with a few shortcuts
const ctx = canvas.getContext("2d");
canvas.width = innerWidth;
canvas.height = innerHeight;
window.addEventListener("resize", () => {
canvas.width = innerWidth;
canvas.height = innerHeight;
waveExample.y2 = waveExample.x2 = innerWidth; // at 45 deg
drawSinWave(waveExample);
})
const waveExample = {
waveLen: 100, // pixels
phase: 50, // mid way
amplitude: 35,
offset: 0,
x1: 20,
y1: 20,
x2: 400, // as vars to let it change to fit resize
y2: 400,
lineWidth : 5,
lineCap : "round",
lineJoin : "round",
strokeStyle : "Red",
}
function drawSinWave(wave) {
ctx.lineWidth = wave.lineWidth;
ctx.lineCap = wave.lineCap;
ctx.lineJoin = wave.lineJoin;
ctx.strokeStyle = wave.strokeStyle;
var vx = wave.x2 - wave.x1;
var vy = wave.y2 - wave.y1;
const dist = Math.sqrt(vx * vx + vy * vy);
vx /= dist;
vy /= dist;
ctx.beginPath();
for (var i = 0; i <= dist + 0.5; i++) {
if (i > dist) { i = dist }
const pos = Math.sin(((i + wave.phase) / wave.waveLen) * Math.PI * 2) * wave.amplitude + wave.offset;
ctx.lineTo(
wave.x1 + vx * i - vy * pos,
wave.y1 + vy * i + vx * pos
);
}
ctx.stroke();
}
drawSinWave(waveExample);
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id=canvas width=4 00 height=4 00></canvas>
The easiest solution is rotating the canvas:
ctx.rotate(45*Math.PI/180);
Although I'm presuming you need the canvas orientation fixed and you need to mathematically alter the way you draw? In which case here's a whole bunch of math about how to plot sine waves at a rotated counterclockwise:
http://mathman.biz/html/rotatingsine.html
I'm creating a map dynamically from a json file but the drawings end up out of screen because of the x,y. What I wanted to do is to move the all drawings to closer to the left and top of the map element that contains the drawing.
I tried couple of things like playing with the top in css but then the div goes on top of other controls. The other idea is to modify the existing coordinates and do some math with it but I want to know if there is a better approach.
Here what I have
var c=[];
c.push(["valu1",120,240]);
c.push(["valu1",130,240]);
c.push(["valu2",120,250]);
c.push(["valu3",130,250]);
c.push(["valu4",120,245]);
c.push(["valu5",130,245]);
//7k more rows will be here.
$("#refresh").on("click",function(){
getdata();
});
function getdata()
{
for (var i in c) {
var x = c[i][1]
var y = c[i][2]
Paint(x,y);
}
}
function Paint(x, y) {
var ctx, cv;
cv = document.getElementById('map');
ctx = cv.getContext('2d');
ctx.strokeStyle = '#666699';
ctx.lineWidth = 1;
ctx.strokeRect(x, y, 10, 5);
ctx.stroke();
}
notice the first row x = 120. what I would like to move closer to the top of my element.
here is the jsfiddle.
JSFIDDLE
Below is an implementation that displaces the points toward the upper left as far as possible. If the map is still too large, it will have to be scaled to fit the canvas.
Note that I have changed some variable names and streamlined the code in various places. To make the canvas larger, see the parameters at the beginning of the start() function.
var data = [
["a", 120, 240],
["b", 130, 240],
["c", 120, 250],
["d", 130, 250],
["e", 120, 245],
["f", 130, 245]
];
function drawBox(context, x, y, width, height) {
context.strokeStyle = '#666699';
context.strokeRect(x, y, width, height);
}
function start() {
// The following are the display parameters. Set them to your liking.
var boxWidth = 10, boxHeight = 5, lineWidth = 2,
canvasWidth = 800, canvasHeight = 600; // Canvas dimensions.
var container = document.getElementById('container'),
canvas = container.getElementsByTagName('canvas')[0],
context = canvas.getContext('2d');
context.lineWidth = lineWidth;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
container.style.width = canvasWidth + 'px';
container.style.height = canvasHeight + 'px';
var n = data.length,
minX = data[0][1],
minY = data[0][2];
for (var i = 1; i < n; ++i) {
var x = data[i][1],
y = data[i][2];
if (x < minX) {
minX = x;
}
if (y < minY) {
minY = y;
}
}
for (var i = 0; i < n; ++i) {
var x = data[i][1], y = data[i][2],
X = lineWidth + x - minX, // Displace the points to upper right.
Y = lineWidth + y - minY; // Add padding to avoid clipping lines.
drawBox(context, X, Y, boxWidth, boxHeight);
}
}
window.onload = start;
#container {
border: 3px solid #eee;
}
<div id="container"><canvas></canvas></div>
I would like to generate a canvas image using gradients in some clever way. I would like the image to looks something like this:
I just can't get my head around it. I need to generate lines in the form and arc - or use gradients with color stops in some clever way. Maybe it would be a lot easier if I converted to HSL and just go through the HUE values?
For example in a rectangle format I could
for (var i = 0; i < h; ++i) {
var ratio = i/h;
var hue = Math.floor(360*ratio);
var sat = 100;
var lum = 50;
line(dc, hslColor(hue,sat,lum), left_margin, top_margin+i, left_margin+w, top_margin+i);
}
Does anybody have any clever tips on how to produce this image using canvas?
This is not perfect (due to drawing steps ...), but it can help you :
http://jsfiddle.net/afkLY/2/
HTML:
<canvas id="colors" width="200" height="200"></canvas>
Javascript:
var canvas = document.getElementById("colors");
var graphics = canvas.getContext("2d");
var CX = canvas.width / 2,
CY = canvas.height/ 2,
sx = CX,
sy = CY;
for(var i = 0; i < 360; i+=0.1){
var rad = i * (2*Math.PI) / 360;
graphics.strokeStyle = "hsla("+i+", 100%, 50%, 1.0)";
graphics.beginPath();
graphics.moveTo(CX, CY);
graphics.lineTo(CX + sx * Math.cos(rad), CY + sy * Math.sin(rad));
graphics.stroke();
}
The idea is to draw the disc line by line with a hue value corresponding to the line direction.
You can change the color base rotation by adding a radius angle to rad variable (adding -pi/2 to rad would make the gradient look like your figure).
EDIT:
I made a new demo that generalizes the concept a bit and renders a rainbow polygon. Here is the CodePen.
To get rid of the small voids beteween the colors, I used quads that overflow to the next color part, except for the last one.
Small adjustment to make it have a white center
var canvas = document.getElementById('colorPicker');
var graphics = canvas.getContext("2d");
var CX = canvas.width / 2,
CY = canvas.height / 2,
sx = CX,
sy = CY;
for (var i = 0; i < 360; i += 0.1) {
var rad = i * (2 * Math.PI) / 360;
var grad = graphics.createLinearGradient(CX, CY, CX + sx * Math.cos(rad), CY + sy * Math.sin(rad));
grad.addColorStop(0, "white");
grad.addColorStop(0.01, "white");
grad.addColorStop(0.99, "hsla(" + i + ", 100%, 50%, 1.0)");
grad.addColorStop(1, "hsla(" + i + ", 100%, 50%, 1.0)");
graphics.strokeStyle = grad;
graphics.beginPath();
graphics.moveTo(CX, CY);
graphics.lineTo(CX + sx * Math.cos(rad), CY + sy * Math.sin(rad));
graphics.stroke();
}
Here is an alternate approach that takes a slightly more functional approach:
var canvas = document.getElementById("radial"),
ctx = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height,
center = { x: width/2, y: height/2 },
diameter = Math.min(width, height);
var distanceBetween = function(x1,y1,x2,y2) {
// Get deltas
var deltaX = x2 - x1,
deltaY = y2 - y1;
// Calculate distance from center
return Math.sqrt(deltaX*deltaX+deltaY*deltaY);
}
var angleBetween = function(x1,y1,x2,y2) {
// Get deltas
var deltaX = x2 - x1,
deltaY = y2 - y1;
// Calculate angle
return Math.atan2(deltaY, deltaX);
}
var radiansToDegrees = _.memoize(function(radians) {
// Put in range of [0,2PI)
if (radians < 0) radians += Math.PI * 2;
// convert to degrees
return radians * 180 / Math.PI;
})
// Partial application of center (x,y)
var distanceFromCenter = _.bind(distanceBetween, undefined, center.x, center.y)
var angleFromCenter = _.bind(angleBetween, undefined, center.x, center.y)
// Color formatters
var hslFormatter = function(h,s,l) { return "hsl("+h+","+s+"%,"+l+"%)"; },
fromHue = function(h) { return hslFormatter(h,100,50); };
// (x,y) => color
var getColor = function(x,y) {
// If distance is greater than radius, return black
return (distanceFromCenter(x,y) > diameter/2)
// Return black
? "#000"
// Determine color
: fromHue(radiansToDegrees(angleFromCenter(x,y)));
};
for(var y=0;y<height;y++) {
for(var x=0;x<width;x++) {
ctx.fillStyle = getColor(x,y);
ctx.fillRect( x, y, 1, 1 );
}
}
It uses a function to calculate the color at each pixel – not the most efficient implementation, but perhaps you'll glean something useful from it.
Note it uses underscore for some helper functions like bind() – for partial applications – and memoize.
Codepen for experimentation.