Seemed simple enough to draw circles and text in an HTML5 canvas, but I get non-intuitive behavior. The circles get drawn nice and pretty, then the more circles I draw, the older circles become more and more octagonal shaped. Very strange to me... Also, the text disappears from the old circles and only appears on the last circle drawn. What's the proper way to do this with canvases?
$("#circles_container").on("click", "#circles_canvas", function(event) {
var canvas = document.getElementById('circles_canvas');
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
var w = 16;
var x = event.pageX;
var y = Math.floor(event.pageY-$(this).offset().top);
ctx.fillStyle = "rgb(200,0,0)";
ctx.arc(x, y, w/2, 0, 2 * Math.PI, false);
ctx.fill();
ctx = canvas.getContext("2d");
ctx.font = '8pt Calibri';
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.fillText('0', x, y+3);
}
});
Just add this near the start of your function :
ctx.beginPath();
You were drawing a path always longer.
Demo in Stack Snippets & JS Fiddle (click on the canvas)
var canvas = document.getElementById('circles_canvas');
canvas.addEventListener("click", function(event) {
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
var w = 16;
var x = Math.floor(event.pageX-this.offsetLeft);
var y = Math.floor(event.pageY-this.offsetTop);
ctx.beginPath();
ctx.fillStyle = "rgb(200,0,0)";
ctx.arc(x, y, w/2, 0, 2 * Math.PI, false);
ctx.fill();
ctx.font = '8pt Calibri';
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.fillText('0', x, y+3);
}
})
canvas {
border: 1px solid black;
}
<h1>Click Canvas Below</h1>
<canvas id="circles_canvas"></canvas>
Related
In the graphic the idea was to put a shadow only on the white circle, but it looks like the shadow effect was applied to the text, which shouldn't happen.
Canvas:
<canvas class="gradient" id="canvas_gradient_chart" height="70"></canvas>
Javascript:
drawChartGradient(canvas, radian, height, metric) {
const rect = canvas.getBoundingClientRect();
const ctx = canvas.getContext("2d");
canvas.width = rect.width;
canvas.height = height;
ctx.beginPath();
const centerY = canvas.height / 2;
const centerX = this.positionXChart(canvas.width, metric, 20);
ctx.arc(centerX, centerY, radian, 0, Math.PI * 2, false);
ctx.fillStyle ="#ffffff";
ctx.filter = 'drop-shadow(2px 2px 5px rgba(0,0,0,0.6))';
ctx.fill();
ctx.font = 'bold 14pt sans-serif';
ctx.textAlign = 'center';
ctx.strokeStyle ='#ffffff'
ctx.stroke();
ctx.fillStyle ="#622BCF"; // <-- Text colour here
ctx.shadowBlur=0;
ctx.shadowColor='transparent';
ctx.fillText(`${metric ? metric : 0}`, centerX, centerY+8);
//ctx.globalCompositeOperation = 'destination-over';
ctx.globalCompositeOperation = 'source-over';
canvas.addEventListener('mousemove', (e) => {
const relativeCoodinates = {
x:parseInt(`${centerX}`),
y:parseInt(`${centerY}`),
r:radian
}
});
ctx.globalCompositeOperation = 'destination-over';
ctx.save();
ctx.restore();
}
positionXChart(size, number, distance) {
return size/(200 + distance) * (parseInt(`${number}`) + 100 + distance / 2);
}
Canvas would render:
You have to reset the filter or the same will be applied to all elements:
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter#value
See working sample:
const canvas = document.getElementById("canvas")
const ctx = canvas.getContext("2d")
ctx.beginPath()
ctx.arc(50, 50, 40, 0, Math.PI * 2)
ctx.fillStyle = "#ffffff"
ctx.filter = 'drop-shadow(2px 2px 5px)'
ctx.fill()
ctx.beginPath()
ctx.font = 'bold 30pt sans-serif'
ctx.textAlign = 'center'
ctx.textBaseline = "middle"
ctx.fillStyle = "#622BCF" // <-- Text colour here
ctx.filter = 'none'
ctx.fillText("80", 50, 50)
<canvas id="canvas" ></canvas>
And the textBaseline = "middle" I think does a good job at keeping the text centered
I was surprised to find out that apparently the canvas API does not allow you to apply gradients to shadows like this:
var grad = ctx.createLinearGradient(fromX, fromY, toX, toY);
grad.addColorStop(0, "red");
grad.addColorStop(1, "blue");
ctx.strokeStyle = grad;
ctx.lineWidth = 3;
ctx.shadowBlur = 10;
ctx.shadowColor = grad; // doesn't seem to work
ctx.beginPath();
ctx.moveTo(fromX, fromY);
ctx.lineTo(toX, toY);
ctx.closePath();
ctx.stroke();
// linear gradient from start to end of line
var canvas = document.getElementById('mycanvas'),
ctx = canvas.getContext('2d'),
fromX = 3,
fromY = 3,
toX = 197,
toY = 197,
grad = ctx.createLinearGradient(fromX, fromY, toX, toY);
canvas.width = 200;
canvas.height = 200;
grad.addColorStop(0, "red");
grad.addColorStop(1, "blue");
ctx.strokeStyle = grad;
ctx.lineWidth = 3;
ctx.shadowBlur = 20;
ctx.shadowColor = grad;
ctx.beginPath();
ctx.moveTo(fromX, fromY);
ctx.lineTo(toX, toY);
ctx.closePath();
ctx.stroke();
body {
background: black
}
<canvas id="mycanvas"></canvas>
One workaround is to simply draw the line/shape/etc. multiple times at different sizes and opacity to get a similar result:
var grad = ctx.createLinearGradient(fromX, fromY, toX, toY);
canvas.width = 200;
canvas.height = 200;
grad.addColorStop(0, "red");
grad.addColorStop(1, "blue");
ctx.strokeStyle = grad;
ctx.lineWidth = 3;
//ctx.shadowBlur = 20;
//ctx.shadowColor = grad;
for (var i = 10; i > 1; i--) {
ctx.lineWidth = i;
ctx.globalAlpha = 1 / i;
ctx.beginPath();
ctx.moveTo(fromX, fromY);
ctx.lineTo(toX, toY);
ctx.closePath();
ctx.stroke();
}
// linear gradient from start to end of line
var canvas = document.getElementById('mycanvas'),
ctx = canvas.getContext('2d'),
fromX = 3,
fromY = 3,
toX = 197,
toY = 197,
grad = ctx.createLinearGradient(fromX, fromY, toX, toY);
canvas.width = 200;
canvas.height = 200;
grad.addColorStop(0, "red");
grad.addColorStop(1, "blue");
ctx.strokeStyle = grad;
ctx.lineWidth = 3;
//ctx.shadowBlur = 20;
//ctx.shadowColor = grad;
for (var i = 10; i > 1; i--) {
ctx.lineWidth = i;
ctx.globalAlpha = 1 / i;
ctx.beginPath();
ctx.moveTo(fromX, fromY);
ctx.lineTo(toX, toY);
ctx.closePath();
ctx.stroke();
}
body {
background: black;
}
<canvas id="mycanvas"></canvas>
Here's the comparison. Although the change is subtle, the right image shows roughly the desired effect.
Is there a better way of doing this? I imagine there's a more efficient way than drawing the same thing multiple times. Does anyone know of a library that provides this kind of functionality?
Use the filter property of the canvas 2d context. MDN filter though (as usual) it does say filter is not supported on Chrome it has been from some time on the Beta version. For IE I do not know and for FF it has been supported for some time. You will have to test for it if you use it.
UPDATE
Support does not seam to be automatic. Though MDN shows support for Firefox you must set the canvas.filters.enable to true (whatever that means, I am sure firefox lovers know) and seams for chrome you must go to chrome://flags then set experimental canvas features to enabled
More
I have added a fallback as there is such limited support. It uses a second canvas to blur the shadow by using the ctx.imageSmoothingEnabled=true; and rendering at a scale one half the blur amount. So if blur is 5 pixels then in background canvas must be one tenth the size. Then on the original canvas render the background canvas at full size with smoothing on.
No the best result and will no be good for lines, but its fast and can be played around with to optimise results.
Snippet to show how to detect support and use.
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
var g = ctx.createLinearGradient(10,10,100,100);
for(var i = 0; i <= 1; i+= 0.05){
g.addColorStop(i,"hsl("+Math.floor(i*360)+",100%,50%)");
}
var gDark = ctx.createLinearGradient(20,20,100,100);
for(var i = 0; i <= 1; i+= 0.05){
gDark.addColorStop(i,"hsl("+Math.floor(i*360)+",100%,30%)");
}
ctx.font = "16px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "hanging";
if(ctx.filter !== undefined){
ctx.fillText("Using filter.",65,125);
ctx.fillStyle = gDark;
ctx.filter = "blur(5px)"; // set the blur
ctx.fillRect(20,20,100,100); // draw the shadow
ctx.fillStyle = g; // set the lighter gradoent
ctx.filter = "blur(0px)"; // remove the blur
ctx.lineWidth = 2;
ctx.strokeStyle = "black"
ctx.fillRect(10,10,100,100); // draw the box
ctx.strokeRect(10,10,100,100); // with line to look nice.
}else{
// fallback method
ctx.fillText("Using Fallback.",60,125);
var can = document.createElement("canvas"); // create a second canvas
can.width = Math.floor(canvas.width/10); // size to make one pixel the
can.height =Math.floor(canvas.height/10); // size of the blur
var ctxS = can.getContext("2d");
ctxS.setTransform(1/10,0,0,1/10,0,0); // set scale so can work in same coords
ctxS.fillStyle = gDark;
ctxS.fillRect(20,20,100,100); // draw the shadow
ctx.imageSmoothingEnabled=true;
ctx.drawImage(can,0,0,canvas.width,canvas.height);
}
ctx.fillStyle = g; // set the lighter gradoent
ctx.lineWidth = 2;
ctx.strokeStyle = "black"
ctx.fillRect(10,10,100,100); // draw the box
ctx.strokeRect(10,10,100,100); // with line to look nice.
#canV {
width:200px;
height:200px;
}
<canvas id="canV" width = 200 height =200></canvas>
I have this code to draw some text on my HTML Canvas :
$("#canvastext").keyup(function(){
ctx.lineWidth = 8;
ctx.font = '20pt Arial';
ctx.strokeStyle = 'black';
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
var text = document.getElementById('canvastext').value;
text = text.toUpperCase();
x = canvas.width/2;
y = canvas.height - canvas.height/4.5;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeText(text, x, y);
ctx.fillText(text, x, y);
});
But why some characters have this strange shape :
Why A, M, V, W have ugly stroke lines?
It's because the linejoin property in canvas is default to miter, and when the line join angle is smaller, it'll create a sharper join at the point, which will extend longer.
Solution:
Set ctx.miterLimit, like ctx.miterLimit=1.
Use other lineJoin value, like round, `
var canvas = document.getElementById('cv');
var canvas2 = document.getElementById('cv2');
var ctx = canvas.getContext('2d');
var ctx2 = canvas2.getContext('2d');
$("#canvastext").keyup(function(){
ctx.lineWidth = 8;
ctx.font = '20pt Arial';
ctx.strokeStyle = 'black';
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.miterLimit = 2;
ctx2.lineWidth = 8;
ctx2.font = '20pt Arial';
ctx2.strokeStyle = 'black';
ctx2.fillStyle = 'white';
ctx2.textAlign = 'center';
ctx2.lineJoin = 'round';
var text = document.getElementById('canvastext').value;
text = text.toUpperCase();
x = canvas.width/2;
y = canvas.height - canvas.height/4.5;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeText(text, x, y);
ctx.fillText(text, x, y);
x = canvas2.width/2;
y = canvas2.height - canvas.height/4.5;
ctx2.clearRect(0, 0, canvas.width, canvas.height);
ctx2.strokeText(text, x, y);
ctx2.fillText(text, x, y);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" id="canvastext"/><br/>
<canvas id="cv" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<canvas id="cv2" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
Use shadow*, instead of stroke*:
ctx.textBaseline = βtopβ
ctx.shadowColor = β#000β
ctx.shadowOffsetX = width;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -width, 0);
I want to draw one circle and a character with shadow on a canvas in a HTML page while loading the page and recreate the image on a button click. I am using this code:
window.onload = function() {
draw();
};
function draw(){
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
ctx.clearRect(0, 0, c.width, c.height);
var width = c.width;
var height = c.height;
//DRAW A CIRCLE
var centerX = Math.floor((Math.random() * width));
var centerY = Math.floor((Math.random() * height));
var radius = Math.floor(Math.random() * 50);
var color = '#f11';
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
//DRAW A CHARACTER WITH SHADOW
var c = "S";
ctx.font = "300% Verdana";
ctx.shadowBlur = 20;
ctx.shadowColor = "black";
ctx.shadowOffsetX = 20;
ctx.shadowOffsetY = 20;
ctx.fillStyle = "#111";
ctx.fillText(c, 10, 90);
}
In HTML I am calling draw function onclick() event of a button named Refresh.
For the first time it is giving desired output by drawing one circle and a character with shadow. As I click on the Refresh button it is drawing both the objects with shadow. I dont want to draw shadow of the circle. Can anyone please tell me the mistake I'm doing here.
You may want to use the CanvasRenderingContext2D.save() method :
window.onload = function() {
draw();
};
document.getElementById("canvas").addEventListener('click', draw);
function draw(){
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
ctx.clearRect(0, 0, c.width, c.height);
var width = c.width;
var height = c.height;
//DRAW A CIRCLE
var centerX = Math.floor((Math.random() * width));
var centerY = Math.floor((Math.random() * height));
var radius = Math.floor(Math.random() * 50);
var color = '#f11';
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
//DRAW A CHARACTER WITH SHADOW
//save the actual context
ctx.save();
var c = "S";
ctx.font = "300% Verdana";
ctx.shadowBlur = 20;
ctx.shadowColor = "black";
ctx.shadowOffsetX = 20;
ctx.shadowOffsetY = 20;
ctx.fillStyle = "#111";
ctx.fillText(c, 10, 90);
//restore it
ctx.restore();
}
canvas{border:1px solid;}
<canvas id="canvas" width="400" height="200"></canvas>
I developed an HTML5 canvas graph that retrieves SQL-stored information and plots them graphically (color-coded) on a HTML5 canvas. The canvas allows scrolling over a timeline to show various events that have occured (Say between 1990 - 2013).
IE works like a charm.
Chrome has issues with the context font being muddy/bleeding effect - i was using monospace 11px, I changed it to verdana later on, but still a bit muddy with chrome. Firefox doesn't have this issue.
Firefox has an issue, where it retrives and plots information on the canvas, but when i click on the canvas to scroll in the past or future of current position on the timeline, the entire canvas dissapears. Chrome doesn't have this issue.
I 've tried to explain my issues on this question, if you need more clarification please ask.
here is the sample code:-
http://jsfiddle.net/WNpKE/16/
(if you click on the link and open it in IE, FireFox, and Chrome, I hope that the issue will become more evident.)
// defining the canvas element
var can = document.getElementById("myCanvas"),
ctx = can.getContext("2d"),
dragging = false,
translated = 0,
lastX = 0,
grid = (function (dX, dY) {
var can = document.createElement("canvas"),
ctx = can.getContext('2d');
can.width = dX;
can.height = dY;
// fill canvas color
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, dX, dY);
// x axis
ctx.strokeStyle = 'orange';
ctx.moveTo(.5, 0.5);
ctx.lineTo(dX + .5, 0.5);
ctx.stroke();
// y axis
ctx.moveTo(.5, .5);
ctx.lineTo(.5, dY + .5);
ctx.stroke();
return ctx.createPattern(can, 'repeat');
})(72, 50);
ctx.save();
/*ctx.scale(1, -1);
ctx.translate(0, -900);*/
// when mouse is clicked on canvas
can.onmousedown = function (e) {
var evt = e || event;
dragging = true;
lastX = evt.offsetX;
}
// when mouse is clicked again and the canvas is deselected
window.onmouseup = function () {
dragging = false;
}
window.onmousemove = function (e) {
var evt = e || event;
if (dragging) {
var delta = evt.offsetX - lastX;
translated += delta;
//console.log(translated);
ctx.restore();
ctx.clearRect(0, 0, 930, 900);
ctx.save();
ctx.translate(translated, 0);
lastX = evt.offsetX;
timeline();
}
}
// Function that draws the timeline on the xy grid along with data points.
function timeline() {
// fill canvas color
ctx.fillStyle = "black";
ctx.fillRect(-translated, 0, 930, 900);
ctx.fillStyle = grid;
ctx.fillRect(-translated, -250, 930, 900);
// y-co-ordinate texts - Home, Office, Emergency, etc...
ctx.strokeStyle = "White";
ctx.font = "10px Verdana";
ctx.strokeText("Home", -translated, 510);
ctx.strokeStyle = "White";
ctx.font = "10px Verdana";
ctx.strokeText("Office", -translated, 460);
ctx.strokeStyle = "White";
ctx.font = "10px Verdana";
ctx.strokeText("Emergency", -translated, 410);
ctx.strokeStyle = "White";
ctx.font = "10px Verdana";
ctx.strokeText("Foster Home", -translated, 360);
ctx.strokeStyle = "White";
ctx.font = "10px Verdana";
ctx.strokeText("SNF", -translated, 310);
ctx.strokeStyle = "White";
ctx.font = "10px Verdana";
ctx.strokeText("LTC", -translated, 260);
ctx.strokeStyle = "White";
ctx.font = "10px Verdana";
ctx.strokeText("Drug/Rehab", -translated, 210);
ctx.strokeStyle = "White";
ctx.font = "10px Verdana";
ctx.strokeText("Hospital", -translated, 160);
ctx.strokeStyle = "White";
ctx.font = "10px Verdana";
ctx.strokeText("Hospice", -translated, 110);
ctx.strokeStyle = "White";
ctx.font = "10px Verdana";
ctx.strokeText("ANP Exams", -translated, 540);
ctx.strokeStyle = "White";
ctx.font = "10px Verdana";
ctx.strokeText("Life Event", -translated, 560);
ctx.strokeStyle = "White";
ctx.font = "10px Verdana";
ctx.strokeText("Care Plan", -translated, 610);
I have changed a bit since this code, but the basic idea of click and scroll is still the same. Thanks.
Use fillText instead of strokeText.
The FF error is happening because the FF event object doesn't have an offsetX property. Use pageX instead.
Updated fiddle: http://jsfiddle.net/WNpKE/18/
Take a look at the highcharts API. It's free and has an enormous amount of features. I've recently used it in a web application effort querying data from an SQL database not unlike what you're doing. It works across all the major browsers.
I am guessing that the canvas element (being a new feature of html5) isn't being rendered the same way between browsers. You may be better off either re-writing in javascript / java or just using the highcharts framework as-is. I realise this isn't a solution to your current problem, but it may save you some time.
Good Luck!