User input data to a create Canvas element pie chart - javascript

I want to create an element, where the end-user can enter its information, and the result is shown in a pie chart.
I have borrowed a pre-made pie chart, and when inserting the data as fixed numbers, there is no issue, it shows the right distribution.
However when adding a user input in this example a range type to substitute one of the data points, it is not showing the results. What am I missing here?
<canvas id="test_canvas" width="400" height="250"></canvas>
<input id="danmark" type="range" min="1000" max ="3000" step="100" placeholder ="2500" value="2500" oninput="myFunction()" >
<script>
// pie chart drawings
function drawPieChart( data, colors, title ){
var canvas = document.getElementById( "test_canvas" );
var context = canvas.getContext( "2d" );
// get length of data array
var dataLength = data.length;
// declare variable to store the total of all values
var total = 0;
// calculate total
for( var i = 0; i < dataLength; i++ ){
// add data value to total
total += data[ i ][ 1 ];
}
// declare X and Y coordinates of the mid-point and radius
var x = 100;
var y = 100;
var radius = 100;
// declare starting point (right of circle)
var startingPoint = 0;
for( var i = 0; i < dataLength; i++ ){
// calculate percent of total for current value
var percent = data[ i ][ 1 ] * 100 / total;
// calculate end point using the percentage (2 is the final point for the chart)
var endPoint = startingPoint + ( 2 / 100 * percent );
// draw chart segment for current element
context.beginPath();
// select corresponding color
context.fillStyle = colors[ i ];
context.moveTo( x, y );
context.arc( x, y, radius, startingPoint * Math.PI, endPoint * Math.PI );
context.fill();
// starting point for next element
startingPoint = endPoint;
// draw labels for each element
context.rect( 220, 25 * i, 15, 15 );
context.fill();
context.fillStyle = "black";
context.fillText( data[ i ][ 0 ] + " (" + data[ i ][ 1 ] + ")", 245, 25 * i + 15 );
}
// draw title
context.font = "20px Arial";
context.textAlign = "center";
context.fillText( title, 100, 225 );
}
function myFunction () {
var Danmark = document.getElementbyId('danmark').value;
var Norge = 850;
var Sverige = 2400;
var Finland = 1000;
var Holland = 3000;
}
// the data, which needs to be calculated based on user input variables.
var data = [ [ "Danmark", Danmark ], [ "NL", Holland ], [ "NO", Norge ], [ "SE", Sverige ], [ "FI", Finland ] ];
var colors = [ "red", "orange", "green", "blue", "purple" ];
// using the function
drawPieChart( data, colors, "Sales" );
</script>
```

You have a typo in
var Danmark = document.getElementbyId('danmark').value;
It should be getElementById not getElementbyId.
Another thing. When you get value from input or textarea it returns string value by default. You have to convert that value in the number format.
let Danmark = document.getElementById('danmark').value;
let converted = parseInt(Danmark);
function checkVals() {
console.log(typeof Danmark);
console.log(typeof converted);
}
<textarea id="danmark" onchange="checkVals()"></textarea>

At the beginning of your drawPieChart function, under your context variable you'll need to add context.clearRect(0, 0, canvas.width, canvas.height);. This will clear the canvas for the next draw, otherwise it will just draw on top of what's already there and you won't be able to see the changes.
So it will look like this:
function drawPieChart( data, colors, title ){
var canvas = document.getElementById( "test_canvas" );
var context = canvas.getContext( "2d" );
context.clearRect(0, 0, canvas.width, canvas.height);

Related

Rendering million elements on an html canvas effectively (and recreate render from server)

I am trying to create a html application which is based on a canvas. The canvas elements should have a huge grid, lets say the size of the grid is 1500 x 700. This results in 1,050,000 cells (that's slightly over a million).
So the first question is essentially how do we render this efficiently. We did ofcourse try a very naive implementation of generating rectangles in a loop.
But the page gets stuck when you try and loop over a million times.
The next question would be that I need to load data from a server to render this grid. I was thinking of having a character represent a color in every single cell of the 1500 x 700 grid. If we were to limit the colors to about 20 (using a letter from the alphabet), the file size seems to be limited to around 1 MB, which is not a problem.
But again after loading this file, the question is how do we write it onto the canvas without causing performance issues.
Also this is sort of a lite version of https://pixelcanvas.io/ that we are trying to accomplish. Which admittedly can seemingly handle a million pixels (or cells) on screen.
How would one go about implementing this efficiently.
Use an ImageData to act as your grid, then can put it to a non-visible canvas and draw that non-visible canvas on your visible one, scaled as you wish.
// viewport size (canvas)
const vw = 500;
const vh = 500;
// image size (ImageData)
const iw = 2500;
const ih = 720;
// to handle the camera we use a DOMMatrix object
// which offers a few handful methods
const camera = new DOMMatrix();
const [ z_input, x_input, y_input ] = document.querySelectorAll( "[type='range']" );
[ z_input, x_input, y_input ].forEach( (elem) => {
elem.oninput = updateCamera;
} );
function updateCamera() {
const z = +z_input.value;
const x = +x_input.value;
const y = +y_input.value;
camera.a = camera.d = z;
camera.e = vw / 2 - (x * z);
camera.f = vh / 2 - (y * z);
draw();
}
const colorinput = document.querySelector( "input[type='color']" );
let color = colorinput.value = "#FF0000";
colorinput.oninput = (evt) => {
color = colorinput.value;
draw();
};
// the visible canvas
const canvas = document.querySelector( "canvas" );
canvas.width = vw;
canvas.height = vh;
const ctx = canvas.getContext( "2d" );
// we hold our pixel's data directly in an ImageData
const img = new ImageData( iw, ih );
// use a 32 bit view to access each pixel directly as a single value
const pixels = new Uint32Array( img.data.buffer );
// an other canvas, kept off-screen
const scaler = document.createElement( "canvas" );
// the size of the ImageData
scaler.width = iw;
scaler.height = ih;
const scalerctx = scaler.getContext( "2d" );
// fill with white, for demo
for(let i=0; i<pixels.length; i++) {
pixels[ i ] = 0xFFFFFFFF;
}
const mouse = { x: 0, y: 0, down: false };
canvas.onmousemove = (evt) => {
const canvasBBox = canvas.getBoundingClientRect();
// relative to the canvas viewport
const x = evt.clientX - canvasBBox.left;
const y = evt.clientY - canvasBBox.top;
// transform it by the current camera
const point = camera.inverse().transformPoint( { x, y } );
mouse.x = Math.round( point.x );
mouse.y = Math.round( point.y );
if( mouse.down ) {
addPixel( mouse.x, mouse.y, color );
}
draw();
};
canvas.onmousedown = (evt) => { mouse.down = true; };
document.onmouseup = (evt) => { mouse.down = false; };
function draw() {
// first draw the ImageData on the scaler canvas
scalerctx.putImageData( img, 0, 0 );
// reset the transform to default
ctx.setTransform( 1, 0, 0, 1, 0, 0 );
ctx.clearRect( 0, 0, vw, vh );
// set the transform to the camera
ctx.setTransform( camera );
// pixel art so no antialising
ctx.imageSmoothingEnabled = false;
// draw the image data, scaled on the visible canvas
ctx.drawImage( scaler, 0, 0 );
// draw the (temp) cursor
ctx.fillStyle = color;
ctx.fillRect( mouse.x, mouse.y, 1, 1 );
}
function addPixel( x, y, color ) {
const index = y * img.width + x;
if( index > 0 && index < pixels.length ) {
pixels[ index ] = parseColor( color );
}
}
function parseColor( str ) {
return Number( "0xFF" + str.slice(1).match(/.{2}/g).reverse().join("") );
}
// initial call
updateCamera();
canvas {
border: 1px solid;
background: ivory;
cursor: none;
}
z:<input type=range min=0.1 max=20 step=0.1 id=z-range><br>
x:<input type=range min=0 max=2500 step=0.1 id=x-range><br>
y:<input type=range min=0 max=720 step=0.1 id=y-range><br>
<input type=color><br>
<canvas width=500 height=500></canvas>
If you need bigger area, use multiple ImageData objects and the same "trick".

Issue with Javascript accepting user data for canvas pie chart

Im trying to generate a simple pie chart using canvas, but its not displaying anything when data is assigned to an array used to draw the chart. I cant seem to find out exactly what the issue is.
Ive tried to convert the array to integers using a loop but it still won't use the data to draw the graph.
function draw() {
var n = document.getElementById("data").value;
var values = n.split(",");
//document.write(values);
var colors = [ "blue", "red", "yellow", "green", "black" ];
var canvas = document.getElementById( "myCanvas");
var context = canvas.getContext( "2d" );
// grey background
context.fillStyle = "rgb(200,200,200)";
context.fillRect (0, 0, 600, 450);
//iterate through array and assign values to integers
for( var i = 0; i < values.length; i++ ){
values[i] = parseInt(values[i]);
}
// get length of data array
var dataLength = values.length;
// variable to store all values
var total = 0;
// calculate total
for( var i = 0; i < dataLength; i++ ){
// add data value to total
total += values[ i ];
}
// declare X and Y coordinates
var x = 100;
var y = 100;
var radius = 100;
// declare starting point
var startingPoint = 0;
for( var i = 0; i < dataLength; i++ ){
// calculate percent of total for current value
var percent = values[ i ][ 1 ] * 100 / total;
// calculate end point using the percentage (2 is the final point for the chart)
var endPoint = startingPoint + ( 2 / 100 * percent );
// draw chart segment for current element
context.beginPath();
// select corresponding color
context.fillStyle = colors[ i ];
context.moveTo( x, y );
context.arc( x, y, radius, startingPoint * Math.PI, endPoint * Math.PI );
context.fill();
// starting point for next element
startingPoint = endPoint;
}
}
<html>
<head>
<title> Easy Graph</title>
<link rel="stylesheet" href="stylesheet.css">
<style type = "text/css">
canvas {
border: 2px solid black;
}
</style>
<script type="text/javascript" src="pie.js">
</script>
</head>
<body>
<h2> Pie chart </h2>
<h2> Enter values seperated by commas</h2>
<input type="text" name="number" id="data"><br>
<input type="button" value="submit" name="submit" onclick = "draw();">
<input type="button" value="Clear" name="Clear" onclick="reset()"><br><br>
<canvas id ="myCanvas" width= "600" height= "300">
</canvas>
</body>
</html>
This like is wrong:
var percent = values[ i ][ 1 ] * 100 / total;
It should be this way
var percent = values[ i ] * 100 / total;
You're trying to get a position in the array that doesn't exist.

p5.js: change text color in for a single word in a sentence

I want to change the color of a single word in a string that I am drawing to the canvas, without having to manually fiddle with text locations and have multiple "text()" function calls.
Is this possible in p5?
Example:
textAlign(CENTER);
text("I want THIS to be in green.",50,50);
Where "THIS" is a different color from the rest of the sentence.
I could use multiple text calls, such as
textAlign(CENTER);
fill(255,0,0);
text("I want ",50,50);
fill(0,255,0);
text("THIS",50 + widthOfFirstPart,50)
fill(255,0,0);
text("to be in green.",50 + widthOfSecondPart,50);
But that seems clunky, UNLESS there is a good way to solve for the "widthOfFirstPart" and "widthOfSecondPart" variables.
I recommend to create a function, which handles an array of tuples, consisting of text and color.
e.g.:
var red = [255, 0, 0];
var green = [0, 255, 0];
var string = [
["I want ", red],
["THIS ", green],
["to be in green.", red]
];
Process the array in a loop, calculate the length of each text part. Sum the length of the text parts, to determine the start position of each part. Of course the text has to be aligned RIGHT (it would be possible to extend the code, to handle other alignments too):
function drawtext( x, y, text_array ) {
var pos_x = x;
for ( var i = 0; i < text_array.length; ++ i ) {
var part = text_array[i];
var t = part[0];
var c = part[1];
var w = textWidth( t );
fill( c );
text( t, pos_x, y);
pos_x += w;
}
}
function setup() {
createCanvas( 500, 200 );
}
function draw() {
background( 126, 192, 238 );
textAlign(LEFT);
var red = [255, 0, 0];
var green = [0, 255, 0];
var string = [
["I want ", red],
["THIS ", green],
["to be in green.", red]
];
drawtext(50, 50, string );
}
function drawtext( x, y, text_array ) {
var pos_x = x;
for ( var i = 0; i < text_array.length; ++ i ) {
var part = text_array[i];
var t = part[0];
var c = part[1];
var w = textWidth( t );
fill( c );
text( t, pos_x, y);
pos_x += w;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.js"></script>

Algorithm for reading image as lines (then get a result of them)?

Is there a algorithm to get the line strokes of a image (ignore curves, circles, etc., everything will be treated as lines, but still similiar to vectors), from their pixels? Then get a result of them, like a Array?
This is how it'd basically work to read
In this way, each row of pixel would be read as 1 horizontal line and I'd like to handle vertical lines also; but if there's a round fat line that takes more than 1 row
it'll be considered one line. Its line width is the same height of pixels it has.
For instance, let's suppose we've a array containing rows of pixels in the (red, green, blue, alpha) format (JavaScript):
/* formatted ImageData().data */
[
new Uint8Array([
/* first pixel */
255, 0, 0, 255,
/* second pixel */
255, 0, 0, 255
]),
new Uint8Array([
/* first pixel */
0, 0, 0, 0,
/* second pixel */
0, 0, 0, 0
])
]
This would be a 2x2px image data, with a straight horizontal red line. So, from this array, I want to get a array containing data of lines, like:
[
// x, y: start point
// tx, ty: end point
// w: line width
// the straight horizontal red line of 1 pixel
{ x: 0, y: 0, tx: 2, ty: 0, w: 1, rgba: [255, 0, 0, 255] }
]
Note: I'd like to handle anti-aliasing.
This is my function to read pixels in the above format:
var getImagePixels = function(img){
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
var imgData = ctx.getImageData(0, 0, img.width, img.height).data;
var nImgData = [];
var offWidth = img.width * 4;
var dataRow = (nImgData[0] = new Uint8Array(offWidth));
for (var b = 0, i = 0; b++ < img.height;) {
nImgData[b] = new Uint8Array(offWidth);
for (var arrI = 0, len = i + offWidth; i < len; i += 4, arrI += 4) {
nImgData[b][arrI] = imgData[i];
nImgData[b][arrI + 1] = imgData[i + 1];
nImgData[b][arrI + 2] = imgData[i + 2];
nImgData[b][arrI + 3] = imgData[i + 3];
}
}
return nImgData;
};
You can find all lines using Hough transform. It will find only lines, no curves or circles. You may need to run edge detection before finding lines. Here is example:
Here you can find opencv example of implementation.
I had a similar image processing question once, you can read it here. But you can basically take the same idea for anything you want to do with an image.
The basic data can be seen as follows:
var img = new Image,
w = canvas.width,
h = canvas.height,
ctx = canvas.getContext('2d');
img.onload = imgprocess;
img.src = 'some.png';
function imgprocess() {
ctx.drawImage(this, 0, 0, w, h);
var idata = ctx.getImageData(0, 0, w, h),
buffer = idata.data,
buffer32 = new Uint32Array(buffer.buffer),
x, y,
x1 = w, y1 = h, x2 = 0, y2 = 0;
//You now have properties of the image from the canvas data. You will need to write your own loops to detect which pixels etc... See the example in the link for some ideas.
}
UPDATE:
Working example of finding color data;
var canvas = document.getElementById('canvas');
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
var buf = new ArrayBuffer(imageData.data.length);
var buf8 = new Uint8ClampedArray(buf);
var data = new Uint32Array(buf);
for (var y = 0; y < canvasHeight; ++y) {
for (var x = 0; x < canvasWidth; ++x) {
var value = x * y & 0xff;
data[y * canvasWidth + x] =
(255 << 24) | // alpha
(value << 16) | // blue
(value << 8) | // green
value; // red
}
}
More examples can be seen here
The author above outlines pixel and line data:
The ImageData.data property referenced by the variable data is a one-dimensional array of integers, where each element is in the range 0..255. ImageData.data is arranged in a repeating sequence so that each element refers to an individual channel. That repeating sequence is as follows:
data[0] = red channel of first pixel on first row
data[1] = green channel of first pixel on first row
data[2] = blue channel of first pixel on first row
data[3] = alpha channel of first pixel on first row
data[4] = red channel of second pixel on first row
data[5] = green channel of second pixel on first row
data[6] = blue channel of second pixel on first row
data[7] = alpha channel of second pixel on first row
data[8] = red channel of third pixel on first row
data[9] = green channel of third pixel on first row
data[10] = blue channel of third pixel on first row
data[11] = alpha channel of third pixel on first row

Javascript: can't represent sinusoidal graph

I want to make a box to move as a sinusoidal graph.
At the point where i am now i simply can't represent the box into the canvas. At the beginning I was able to, but after working out the trigonometry part the box disappeared and a get no error...
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="canvas" width="600" height="300" style="background-color:red"></canvas>
<script type="text/javascript">
var canvas = document.querySelector("canvas");//isoute me document.getElementsByTagName()
var ctx = canvas.getContext("2d");
var can_width = canvas.width;
var can_height = canvas.height;
var x,y;
function PVector(x_,y_){
var y = [];
var x = [0, Math.PI/6, Math.PI/4, Math.PI/3, Math.PI/2, 2/3*Math.PI, 3/4*Math.PI,
5/6*Math.PI, Math.PI, 7/6*Math.PI, 5/4*Math.PI, 4/3*Math.PI, 3/2*Math.PI,
5/3*Math.PI, 7/4*Math.PI, 11/6*Math.PI, 2*Math.PI];
for (var i=0, len=x["length"]; i<len; i++){
var A;
A = Math.sin(x[i]);
y.push(A);
}console.log(y);console.log(x);
return{
x:x,
y:y
};
}
var Point = {
location : {x:0, y: can_height/2},//initial location
velocity : new PVector(x,y),
display : ctx.fillRect(can_width/2,can_height/2 , 25, 25),//initial position of the box
step : function(){
this.location.x += this.velocity.x;
this.location.y += this.velocity.y;
},
display : function(){
ctx.fillRect(this.location.x, this.location.y, 25, 25);
}
};
function update(){
Point["step"]();
ctx.clearRect(0,0, can_width, can_height);
Point["display"]();
window.setTimeout(update, 1000/30);
}
function init(){
update();
}
init();
</script>
</body>
Problem
In your PVector object you are returning Arrays for x and y, while you use them as values in the step() method. This will cause the entire array to be added as a string.
Solution
You need something that traverse that array. Here is an example, it may not be the result you're after, but it shows the principle which you need to apply:
// first note the return type here:
function PVector(x_,y_){
var y = [];
var x = [0, Math.PI/6, Math.PI/4, Math.PI/3, Math.PI/2, 2/3*Math.PI,
...snipped---
return {
x:x, // x and y are arrays
y:y
};
}
var Point = {
location: {
x: 0,
y: can_height / 2,
step: 0 // some counter to keep track of array position
}, //initial location
velocity: new PVector(x, y),
step: function () {
this.location.step++; // increase step for arrays
// using modulo will keep the step as a valid value within the array length:
// if step = 7 and length = 5, index will become 2 (sort of "wrap around")
var indexX = this.location.step % this.velocity.x.length;
var indexY = this.location.step % this.velocity.y.length
this.location.x += this.velocity.x[indexX];
this.location.y += this.velocity.y[indexY];
},
...
Updated fiddle
Tip: I would as Robin in his answer, recommend to simplify the sinus calculation. Sinus-tables are good when performance is needed and the browser can't keep up (ie. will thousands of objects), but in simpler scenario, direct calculation will work too.
If your goal is just to have a box moving in a sinusoidal graph, it can be done simpler.
This jsfiddle shows a slightly simpler example of a box moving in a sinusoidal graph where I just removed parts of your code and calculate the path with Math.sin and use time instead of precalculated values for x.
function update(){
time += 0.1;
ctx.clearRect(0,0, can_width, can_height);
x = time;
y = (can_height/2)+(can_height/2)*Math.sin(time);
console.log(x, y);
ctx.fillRect(x*16, y, 25, 25);
window.setTimeout(update, 1000/30);
}
The variables are modified to make it look ok on the canvas. You can edit the addition to time, and the altitude and base line for y, to fit your needs.
If you need to follow the specification in your code, look at the answer by Ken.
Since you want a sinusoidal move, it makes sense to use... the sin function.
The formula for a sinusoidal move is :
y = maxValue * sin ( 2 * PI * frequency * time ) ;
where the frequency is in Herz (== 'number of times per second') and time is in second.
Most likely you'll use Date.now(), so you'll have a time in millisecond, that you need to convert. Since the value of PI should not change in the near future, you can compute once the magic number
k = 2 * PI / 1000 = 0.006283185307179587
and the formula becomes :
y = sin( k * frequency * Date.now() );
Here's a simple example on how to use the formula :
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
var can_width = canvas.width;
var can_height = canvas.height;
// magic number
var k = 0.006283185307179587;
// oscillations per second
var frequency = 1/5;
// ...
var amplitude = can_width / 8;
function animate() {
ctx.clearRect(0,0,can_width, can_height);
ctx.fillStyle= '#000';
var y = amplitude * Math.sin ( k * frequency * Date.now() );
ctx.fillRect(can_width/2, can_height/2 + y, 20, 20 );
}
setInterval(animate, 30);
<canvas id="canvas" width="400" height="200" style="background-color:red"></canvas>

Categories

Resources