Canvas/JavaScript: How to adjust position of slices in donutgraph using canvas? - javascript

I am trying to draw a donut pie chart using Canvas. Which is almost done but facing some issue in adjusting position of slices.
Current:
Expected:
enter code here
http://jsfiddle.net/RgLAU/1/
I want 1.) yellow/blue to draw from top 2.) want to write some text inside the donut.
Here is my work upto now:
http://jsfiddle.net/RgLAU/1/

arc() method starts from an horizontal line, on the right of your shape, at middle y position of the shape's height.
You will need to add an offset to each of your start and end angle value.
For your text, I'm not sure what it should display, but setting the context's textAlign = "center" and textBaseline = "middle" will make it easy to position anywhere.
A rough uncleaned dump of your modified code :
var canvas = document.getElementById("chart");
var chart = canvas.getContext("2d");
function drawdountChart(canvas) {
// text options
chart.textAlign = "center";
chart.textBaseline = "middle";
chart.font = "25px sans-serif";
// where is our arc start angle
var offset = 1.5 * Math.PI;
this.x, this.y, this.radius, this.lineWidth, this.strockStyle, this.from, this.to = null;
this.set = function(x, y, radius, from, to, lineWidth, strockStyle) {
this.x = x;
this.y = y;
this.radius = radius;
this.from = from;
this.to = to;
this.lineWidth = lineWidth;
this.strockStyle = strockStyle;
}
this.draw = function(data) {
canvas.beginPath();
canvas.lineWidth = this.lineWidth;
canvas.strokeStyle = this.strockStyle;
canvas.arc(this.x, this.y, this.radius, this.from + offset, this.to + offset);
canvas.stroke();
var numberOfParts = data.numberOfParts;
var parts = data.parts.pt;
var colors = data.colors.cs;
var df = 0;
for (var i = 0; i < numberOfParts; i++) {
canvas.beginPath();
canvas.strokeStyle = colors[i];
canvas.arc(this.x, this.y, this.radius, df + offset, df + (Math.PI * 2) * (parts[i] / 100) + offset);
canvas.stroke();
df += (Math.PI * 2) * (parts[i] / 100);
}
chart.fillStyle = 'white'
chart.fillText('hello', this.x, this.y);
}
}
var data = {
numberOfParts: 4,
parts: {
"pt": [20, 30, 25, 25]
}, //percentage of each parts
colors: {
"cs": ["red", "green", "blue", "yellow"]
} //color of each part
};
var drawDount = new drawdountChart(chart);
drawDount.set(150, 150, 100, 0, Math.PI * 2, 30, "#fff");
drawDount.draw(data);
<canvas id="chart" width="500" height="500" style="background-color:black"> </canvas>

Related

Flatten 3d Coordinates to 2d Coordinates

I am trying to make some code that can take in 3d coordinates (x, y, z), and return 2d coordinates (x, y) by translating the x and y values towards the perspective point based on the z value.
My code is :
translate() {
this.distance = Math.sqrt((this.x**2)+(this.y**2));
this.bear = (180/Math.PI)*(Math.asin(this.y/this.distance));
this.transX = (this.x + Math.cos(this.bear)*this.z);
this.transY = (this.y + Math.sin(this.bear)*this.z);
}
and gets me this:
To me, the code looks like it should just move the non-square points inwards but it doesn't.
Does anyone have any ideas to make this work? or are there other ways to do this?
To get a perspective projection at it's simplest form, you don't have to do much. We simply determine a scale and apply the following calculation to all 3d coordinates:
projectedPoint = point * scale / (pointZ + scale)
Here's an example:
class Point {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
}
let cube = [
new Point(-50, -50, -50), new Point(50, -50, -50), new Point(50, 50, -50), new Point(-50, 50, -50),
new Point(-50, -50, 50), new Point(50, -50, 50), new Point(50, 50, 50), new Point(-50, 50, 50)
];
function project(vertices) {
let scale = 200;
let projectedX, projectedY, x, y, z, point;
let centerX = canvas.width / 2;
let centerY = canvas.height / 2;
for (let a = 0; a < vertices.length; a++) {
point = vertices[a];
ctx.beginPath();
projectedX = centerX + point.x * scale / (point.z + scale);
projectedY = centerY + point.y * scale / (point.z + scale);
ctx.arc(projectedX, projectedY, 5, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
}
}
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
ctx.fillStyle = "blue";
project(cube);
<canvas id="canvas"></canvas>

All circles being drawn in the same colour [duplicate]

This question already has answers here:
Drawing lines with canvas by using for loop
(2 answers)
Closed 2 years ago.
Shortly summarized the problem is this. I want to draw two circles on the canvas with different colors. For some reason, they are drawn in the same color, even though the console log I have placed in is switching between "green" and "blue". Sorry that some of the variable names are in my native language if that poses a problem just ask and I'll translate it.
var bodyEl = document.querySelector("body");
var canvasEl = document.createElement("canvas");
var height = window.innerHeight;
var width = window.innerWidth;
canvasEl.height = height;
canvasEl.width = width;
bodyEl.appendChild(canvasEl);
var ctx = canvasEl.getContext("2d");
var obj = [];
class ball {
constructor(radius, farge, xPosisjon, yPosisjon) {
this.x = xPosisjon;
this.y = yPosisjon;
this.rad = radius;
this.farge = farge;
}
get areal() {
let areal = "areal: " + (Math.PI * this.rad * this.rad + "px");
return (areal);
}
tegn() {
//console.log(this.farge);
ctx.fillStyle = this.farge;
ctx.arc(this.x, this.y, this.rad, 0, 2 * Math.PI);
ctx.fill();
}
}
obj.push(new ball(20, "green", 100, 100));
obj.push(new ball(30, "blue", 500, 300));
setInterval(() => {
obj.forEach(x => {
x.tegn();
});
}, 30);
You need to add a ctx.beginPath().
The reason you are seeing the same color is related to the same problem found in this question: Drawing lines with canvas by using for loop. If you don't use beginPath(), you keep pushing draw commands to the same (root) path and then drawing the ever increasingly complex path.
You have to use beginPath to start a sub-path. ctx.fill() will close the sub-path. The closePath is optional.
The third, and an optional step, is to call closePath(). This method
tries to close the shape by drawing a straight line from the current
point to the start. If the shape has already been closed or there's
only one point in the list, this function does nothing.
var bodyEl = document.querySelector("body");
var canvasEl = document.createElement("canvas");
var height = window.innerHeight;
var width = window.innerWidth;
canvasEl.height = height;
canvasEl.width = width;
bodyEl.appendChild(canvasEl);
var ctx = canvasEl.getContext("2d");
var obj = [];
class ball {
constructor(radius, farge, xPosisjon, yPosisjon) {
this.x = xPosisjon;
this.y = yPosisjon;
this.rad = radius;
this.farge = farge;
}
get areal() {
let areal = "areal: " + (Math.PI * this.rad * this.rad + "px");
return (areal);
}
tegn() {
//console.log(this.farge);
ctx.beginPath();
ctx.fillStyle = this.farge;
ctx.arc(this.x, this.y, this.rad, 0, 2 * Math.PI);
ctx.fill();
}
}
obj.push(new ball(20, "green", 100, 100));
obj.push(new ball(30, "blue", 500, 300));
setInterval(() => {
ctx.clearRect(0,0,500,500);
obj.forEach(x => {
x.tegn();
});
}, 1000);
your paths are being drawn in the correct colors, but your second (blue) arc is drawing on top of your first (green) one. at the top of your tegn method, add a call to ctx.beginPath() to let your canvas know that your paths should be independent.
You missed beginPath.
var bodyEl = document.querySelector("body");
var canvasEl = document.createElement("canvas");
var height = window.innerHeight;
var width = window.innerWidth;
canvasEl.height = height;
canvasEl.width = width;
bodyEl.appendChild(canvasEl);
var ctx = canvasEl.getContext("2d");
var obj = [];
class ball {
constructor(radius, farge, xPosisjon, yPosisjon) {
this.x = xPosisjon;
this.y = yPosisjon;
this.rad = radius;
this.farge = farge;
}
get areal() {
let areal = "areal: " + (Math.PI * this.rad * this.rad + "px");
return (areal);
}
tegn() {
//console.log(this.farge);
ctx.beginPath();
ctx.fillStyle = this.farge;
ctx.arc(this.x, this.y, this.rad, 0, 2 * Math.PI);
ctx.fill();
}
}
obj.push(new ball(20, "green", 100, 100));
obj.push(new ball(30, "blue", 500, 300));
setInterval(() => {
obj.forEach(x => {
x.tegn();
});
}, 30);

How to get coordinates of every circle from this Canvas

I need to create a pattern where 5 circles connected by lines to a middle main circle.
So I have created dynamically by rotating in some certain angle. Now I need each and every circle's x and y axis coordinates for capturing the click events on every circle.
Please help me how to find out of coordinates of every circle?
var canvas, ctx;
function createCanvasPainting() {
canvas = document.getElementById('myCanvas');
if (!canvas || !canvas.getContext) {
return false;
}
canvas.width = 600;
canvas.height = 600;
ctx = canvas.getContext('2d');
ctx.strokeStyle = '#B8D9FE';
ctx.fillStyle = '#B8D9FE';
ctx.translate(300, 250);
ctx.arc(0, 0, 50, 0, Math.PI * 2); //center circle
ctx.stroke();
ctx.fill();
drawChildCircles(5);
fillTextMultiLine('Test Data', 0, 0);
drawTextInsideCircles(5);
}
function drawTextInsideCircles(n) {
let ang_unit = Math.PI * 2 / n;
ctx.save();
for (var i = 0; i < n; i++) {
ctx.rotate(ang_unit);
//ctx.moveTo(0,0);
fillTextMultiLine('Test Data', 200, 0);
ctx.strokeStyle = '#B8D9FE';
ctx.fillStyle = '#B8D9FE';
}
ctx.restore();
}
function drawChildCircles(n) {
let ang_unit = Math.PI * 2 / n;
ctx.save();
for (var i = 0; i < n; ++i) {
ctx.rotate(ang_unit);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(100,0);
ctx.arc(200, 0, 40, 0, Math.PI * 2);
let newW = ctx.fill();
ctx.stroke();
}
ctx.restore();
}
function fillTextMultiLine(text, x, y) {
ctx.font = 'bold 13pt Calibri';
ctx.textAlign = 'center';
ctx.fillStyle = "#FFFFFF";
// Defining the `textBaseline`…
ctx.textBaseline = "middle";
var lineHeight = ctx.measureText("M").width * 1.2;
var lines = text.split("\n");
for (var i = 0; i < lines.length; ++i) {
// console.log(lines);
if (lines.length > 1) {
if (i == 0) {
y -= lineHeight;
} else {
y += lineHeight;
}
}
ctx.fillText(lines[i], x, y);
}
}
createCanvasPainting();
<canvas id="myCanvas"></canvas>
The problem here is that you are rotating the canvas matrix and your circles are not aware of their absolute positions.
Why don't you use some simple trigonometry to determine the center of your circle and the ending of the connecting lines ?
function lineToAngle(ctx, x1, y1, length, angle) {
angle *= Math.PI / 180;
var x2 = x1 + length * Math.cos(angle),
y2 = y1 + length * Math.sin(angle);
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
return {x: x2, y: y2};
}
Ref: Finding coordinates after canvas Rotation
After that, given the xy center of your circles, calculating if a coord is inside a circle, you can apply the following formula:
Math.sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)) < r
Ref: Detect if user clicks inside a circle

Create a collision region on canvas elements That interacts with mouse Events

I want to create a collision region around a canvas element that enables me to interact with that element using mouse events width vanilla javascript.
To elaborate more on my problem here is the following:
at first I make an arc segment constructor with x, y, radius, beginAngle, endAngle, and a color arguments
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
/* arc class constructor */
function ArcSegment(x, y, radius, beginAngle, endAngle, segColor) {
this.x = x;
this.y = y;
this.radius = radius;
this.beginAngle = beginAngle;
this.endAngle = endAngle;
this.segColor = segColor;
this.update = function() {
this.draw();
}
this.draw = function(){
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, this.beginAngle, this.endAngle, false);
ctx.lineWidth = 20;
ctx.strokeStyle = this.segColor;
ctx.stroke();
}
}
Secondly, i add some value to create those arc segments
/* x, y, radius, startAngle, endAngle and color */
var centerX = canvas.width/2;
var centerY = canvas.height/2;
var radiuses = [
100,
120
];
var pi = Math.PI;
var segmentStart = [
pi/2,
0
];
var segmentRotation = [
1.4*pi,
0.2*pi
];
var segmentColors = [
"#133046",
"#15959F"
];
Then, i draw Them on the canvas.
var segment1 = new ArcSegment(centerX, centerY, radiuses[0], segmentStart[0], segmentStart[0]+segmentRotation[0], segmentColors[0]);
segment1.update();
var segment2 = new ArcSegment(centerX, centerY, radiuses[1], segmentStart[1], segmentStart[1]+segmentRotation[1], segmentColors[1]);
segment2.update();
and here is the result:
What i want now is a way to create a collision detection on top of each arc segment created, so when a mouse is clicked or moved on top of that specific arc segment
a sequence of events can occur (like a rotation animation for example or so...).
all the research i've done suggest to get the x and y value of a rectangle and calculate the distance of mouse position (mouse.x, mouse.y) and the length of the rectangle, but that method doesn't work with an arc segment with a lineWidth property.
Any help on the subject would be very appreciated.
Below is a pure mathematical approach, the key here is the code isPointInside
// Classes
function Arc(x, y, angle, arc, radius, colour, highlightColour) {
this.x = x;
this.y = y;
this.angle = angle;
this.arc = arc;
this.radius = radius;
this.colour = colour;
this.highlightColour = highlightColour;
this.highlighted = false;
this.lineWidth = 20;
}
Arc.prototype = {
isPointInside: function(x, y) {
var _x = x - this.x;
var _y = y - this.y;
var distance = Math.sqrt(_x * _x + _y * _y);
var invDistance = 1.0 / distance;
var angle = Math.acos(
_x * Math.cos(this.angle) * invDistance +
_y * Math.sin(this.angle) * invDistance
);
return distance > (this.radius - this.lineWidth/2) &&
distance < (this.radius + this.lineWidth/2) &&
angle < this.arc/2;
},
render: function(ctx) {
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.highlighted ? this.highlightColour : this.colour;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, this.angle - this.arc/2, this.angle + this.arc/2, false );
ctx.stroke();
}
};
// Variables
var canvas = null;
var ctx = null;
var arcs = [];
// Functions
function draw() {
ctx.fillStyle = "gray";
ctx.fillRect(0, 0, 999, 999);
for (var i = 0; i < arcs.length; ++i) {
arcs[i].render(ctx);
}
}
// Event Listeners
function onMouseMove(e) {
var bounds = canvas.getBoundingClientRect();
var x = e.clientX - bounds.left;
var y = e.clientY - bounds.top;
for (var i = 0; i < arcs.length; ++i) {
arcs[i].highlighted = arcs[i].isPointInside(x, y);
}
draw();
}
// Entry Point
onload = function() {
canvas = document.getElementById("canvas");
canvas.onmousemove = onMouseMove;
ctx = canvas.getContext("2d");
arcs.push(new Arc(190, 75, 0.2, 1.8, 60, "blue", "lime"));
arcs.push(new Arc(90, 75, 3.5, 4.2, 60, "red", "lime"));
draw();
}
<canvas id="canvas"></canvas>

How I can set text to canvas circle

I used this question and I create shape like this and but now I don't know how I can set text to each circle in just first time click? (like tic tac toe)
Here you go! - I merged it for ease. Just click on circle to see text on it.
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var radius = 70;
var lineWidth = 5;
var cols = 3;
var rows = 2;
var distance = 50;
var circles = [];
//click circle to write
canvas.onclick = function(e) {
// correct mouse coordinates:
var rect = canvas.getBoundingClientRect(), // make x/y relative to canvas
x = e.clientX - rect.left,
y = e.clientY - rect.top,
i = 0, circle;
// check which circle:
while(circle = circles[i++]) {
context.beginPath(); // we build a path to check with, but not to draw
context.arc(circle.x, circle.y, circle.radius, 0, 2*Math.PI);
if (context.isPointInPath(x, y) && !circle.clicked) {
circle.clicked = true;
context.fillStyle = "blue";
context.font = "bold 34px Arial";
context.textAlign="center";
context.fillText("Yeah", circle.x, circle.y);
break;
}
}
};
//draw circles
for (var i = 0; i < rows; i++) {
for (var j = 0; j < cols; j++) {
// Draw circle
var offset = radius * 2 + lineWidth + distance;
var center = radius + lineWidth;
var x = j * offset + center;
var y = i * offset + center;
circles.push({
id: i + "," + j, // some ID
x: x,
y: y,
radius: radius,
clicked:false
});
console.log(circles)
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fillStyle = 'green';
context.fill();
context.lineWidth = lineWidth;
context.strokeStyle = '#003300';
if (j != cols - 1) {
// Draw horizontal line
var hLineX = x + radius;
var hLineY = y;
context.moveTo(hLineX, hLineY);
context.lineTo(hLineX + distance + lineWidth, hLineY);
}
if (i > 0) {
// Draw vertical line
var vLineY = y - radius - distance - lineWidth;
context.moveTo(x, vLineY);
context.lineTo(x, vLineY + distance + lineWidth);
}
context.stroke();
}
}
<div id="ways" style="width:1000px;margin:0 auto;height:100%;">
<canvas id="canvas" width="1000" height="1000"></canvas>
</div>
Happy Helping!

Categories

Resources