Canvas - Creating a partially solid and partially dashed line - javascript

I'm trying to create a line in HTML canvas that is part solid and part dashed. Here is my code
ctx.moveTo(10,10);
ctx.lineTo(70,70);
ctx.setLineDash([5,15]);
ctx.lineTo(100,100);
ctx.stroke();
However this draws the entire line solid. I tried to add setLineDash before I drew the final line segment but that didn't work. Where did I go wrong?

setLineDash will apply for the whole path.
Either create a new Path by calling
//yourfirstLine();
ctx.stroke();
ctx.beginPath()
//yourSecondLine();
var ctx = c.getContext('2d')
ctx.beginPath()
ctx.moveTo(10,10);
ctx.lineTo(70,70);
ctx.stroke();
ctx.beginPath()
ctx.setLineDash([5,15]);
ctx.moveTo(80,80);
ctx.lineTo(100,100);
ctx.stroke();
<canvas id="c"></canvas>
or calculate the dashArray accordingly :
(here for the same result, it would be ctx.setLineDash([85,15,30])).
var ctx = c.getContext('2d')
ctx.beginPath()
ctx.setLineDash([85,15,30]);
ctx.moveTo(10,10);
ctx.lineTo(70,70);
ctx.lineTo(100,100);
ctx.stroke();
<canvas id="c"></canvas>
Personnaly, I would go for the former.

try adding this before your code:
ctx.setLineDash([1, 5]);

Related

HTML Canvas : Fill Arc based complex shape

Below is a JSON structure which draws arcs with different start and end angle
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var elementDetail = {"element":[{"type":"ARC","x1":510,"y1":10,"x2":74.585653306513848,"y2":74.585653306514814,"r":1500,"xm":510,"ym":1510,"alpha":4.4178734952765524,"beta":4.71238898038469,"color":2},{"type":"ARC","x1":74.585653306514587,"y1":74.585653306514928,"x2":10,"y2":510,"r":1500,"xm":1510,"ym":510,"alpha":3.1415926535897931,"beta":3.43610813869793,"color":2},{"type":"ARC","x1":10,"y1":510,"x2":74.585653306514587,"y2":945.41434669348541,"r":1500,"xm":1510,"ym":510,"alpha":2.8470771684816563,"beta":3.1415926535897931,"color":2},{"type":"ARC","x1":74.585653306514871,"y1":945.41434669348541,"x2":510,"y2":1010,"r":1500,"xm":510,"ym":-490,"alpha":1.5707963267948966,"beta":1.8653118119030334,"color":2},{"type":"ARC","x1":510,"y1":1010,"x2":945.4143466934853,"y2":945.41434669348541,"r":1500,"xm":510,"ym":-490,"alpha":1.2762808416867597,"beta":1.5707963267948966,"color":2},{"type":"ARC","x1":945.41434669348541,"y1":945.41434669348541,"x2":1010,"y2":510,"r":1500,"xm":-490,"ym":510,"alpha":0,"beta":0.29451548510813697,"color":2},{"type":"ARC","x1":1010,"y1":510,"x2":945.41434669348519,"y2":74.585653306514132,"r":1500,"xm":-490,"ym":510,"alpha":5.9886698220714489,"beta":6.2831853071795862,"color":2},{"type":"ARC","x1":945.41434669348553,"y1":74.585653306514587,"x2":510,"y2":10,"r":1500,"xm":510,"ym":1510,"alpha":4.71238898038469,"beta":5.006904465492827,"color":2},{"type":"POINT","x1":510,"y1":510,"color":7}]}
ctx.beginPath();
elementDetail.element.map((elem, index) => {
ctx.arc(elem.xm, elem.ym, elem.r, elem.alpha, elem.beta);
})
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "#6fd0ff";
ctx.fill();
canvas{ zoom:.25}
<canvas id="myCanvas" width="1200" height="1200"></canvas>
The shape that I need to obtain is this:
I was able to loop through the json and fill draw stroke but not able to fill color inside the shape
I need help to fill the shape with color
Thanks
To avoid weird results, in complex shapes , I would rather perform the drawing in two steps :
Solid body rendering: draw and fill the shape
Outline rendering : draw the shape outter stroke
Note : When drawing arcs you need to know that ctx.arc() will create a line from the last coordinates of the current path, to the first position of the arc.
In order to avoid it, you need to call moveTo() to lift the drawing pen to the first position of the arc.
var elementDetail = {"element":[{"type":"ARC","x1":510,"y1":10,"x2":74.585653306513848,"y2":74.585653306514814,"r":1500,"xm":510,"ym":1510,"alpha":4.4178734952765524,"beta":4.71238898038469,"color":2},{"type":"ARC","x1":74.585653306514587,"y1":74.585653306514928,"x2":10,"y2":510,"r":1500,"xm":1510,"ym":510,"alpha":3.1415926535897931,"beta":3.43610813869793,"color":2},{"type":"ARC","x1":10,"y1":510,"x2":74.585653306514587,"y2":945.41434669348541,"r":1500,"xm":1510,"ym":510,"alpha":2.8470771684816563,"beta":3.1415926535897931,"color":2},{"type":"ARC","x1":74.585653306514871,"y1":945.41434669348541,"x2":510,"y2":1010,"r":1500,"xm":510,"ym":-490,"alpha":1.5707963267948966,"beta":1.8653118119030334,"color":2},{"type":"ARC","x1":510,"y1":1010,"x2":945.4143466934853,"y2":945.41434669348541,"r":1500,"xm":510,"ym":-490,"alpha":1.2762808416867597,"beta":1.5707963267948966,"color":2},{"type":"ARC","x1":945.41434669348541,"y1":945.41434669348541,"x2":1010,"y2":510,"r":1500,"xm":-490,"ym":510,"alpha":0,"beta":0.29451548510813697,"color":2},{"type":"ARC","x1":1010,"y1":510,"x2":945.41434669348519,"y2":74.585653306514132,"r":1500,"xm":-490,"ym":510,"alpha":5.9886698220714489,"beta":6.2831853071795862,"color":2},{"type":"ARC","x1":945.41434669348553,"y1":74.585653306514587,"x2":510,"y2":10,"r":1500,"xm":510,"ym":1510,"alpha":4.71238898038469,"beta":5.006904465492827,"color":2},{"type":"POINT","x1":510,"y1":510,"color":7}]}
let canvas = document.getElementById("myCanvas");
let ctx = canvas.getContext("2d")
// draw the solid body
ctx.beginPath();
elementDetail.element.map((elem, index) => {
ctx.moveTo(0,0);
ctx.arc(elem.xm, elem.ym, elem.r, elem.alpha, elem.beta);
})
ctx.fillStyle = "#6fd0ff";
ctx.fill();
ctx.closePath();
// draw the outline
elementDetail.element.map((elem, index) => {
ctx.beginPath();
ctx.arc(elem.xm, elem.ym, elem.r, elem.alpha, elem.beta);
ctx.stroke();
})
ctx.closePath();
canvas{ zoom:.25}
<canvas id="myCanvas" width="1200" height="1200"></canvas>
Have you try to use context.fillStyle="your shape colour"?
I think you might use a path to build shape, so every time you start path, you need to assign a colour to every shape.
Try to draw arc and polygon seperately.
function drawGraphics(ctx){
ctx.beginPath();
ctx.fillStyle="#6fd0ff"
elementDetail.element.reverse().map((elem, index) => {
if(elem.type==="ARC"&&index<=8){
ctx.arc(elem.xm, elem.ym, elem.r, elem.alpha, elem.beta);
}
})
ctx.stroke()
ctx.fill();
ctx.closePath();
}

Html5 Canvas stroke transparent gradient line

How do I create a transparent gradient stroke that using html5 canvas? I need it to go from one point to another and look like the below image.
At the moment I have got this:
const gradient = ctx.createLinearGradient(1, 0, 100, 0);
gradient.addColorStop(0, '#fff');
gradient.addColorStop(1, '#d29baf');
ctx.lineWidth = 30;
ctx.strokeStyle = gradient;
ctx.beginPath();
ctx.moveTo(fromXPos, fromYPos);
ctx.lineTo(toXPos, toYPos);
ctx.stroke();
This makes it look like a solid block though like:
Thanks.
Fill a shape
Use a shape and fill it with the gradient.
You can use CSS colour type rgba(red,green,blue,alpha) where red,green,blue are values from 0-255 and alpha is 0 transparent to 1 opaque.
To create a shape you start with ctx.beginPath() to create a new shape then use lineTo(x,y) to mark out each corner. If you want to add another shape using the same fill or stroke you use ctx.moveTo(x,y) to move to the first point.
Note many people use ctx.beginPath(); ctx.moveTo(x,y); but that works just the same as ctx.beginPath(); ctx.lineTo(x,y); As the first point after beginPath is always converted to a moveTo for any type of path object.
const ctx = canvas.getContext("2d");
// draw first box (left of canvas)
ctx.fillStyle = "#ab7383";
ctx.fillRect(20,100,50,50);
// draw second box (to right of first)
ctx.fillStyle = "#904860";
ctx.fillRect(100,20,50,130);
// gradient from top of second box to bottom of both boxes
const g = ctx.createLinearGradient(0, 20, 0, 150);
g.addColorStop(0, `rgba(${0xd2},${0xba},${0xaf},1`); // opaque
g.addColorStop(1, `rgba(${0xd2},${0xba},${0xaf},0`); // transparent
ctx.fillStyle = g;
ctx.beginPath();
ctx.lineTo(70, 100); // top right of first box
ctx.lineTo(100, 20); // top left of second box
ctx.lineTo(100, 150); // bottom left of second box
ctx.lineTo(70, 150); // bottom right of first box
ctx.fill(); // fill the shape
<canvas id="canvas" style="border:2px solid black"></canvas>

Which of the two overlapping graphs will ctx.fill method fill?

I have two slightly different pieces of code which produce different results. In the first piece, I draw the inside triangle first, then the outside square. In the second piece, I draw the outside square first, then the inside triangle. However, for the second piece of code, the entire square is filled. Why is this happening?
<body>
<canvas id="c" width="300" height="300"></canvas>
<script>
var c = document.getElementById('c');
var ctx = c.getContext('2d');
ctx.moveTo(75 ,75);
ctx.lineTo(125, 75);
ctx.lineTo(125, 125);
ctx.lineTo(75, 75);
ctx.fill();
ctx.moveTo(50, 50);
ctx.lineTo(150,50);
ctx.lineTo(50,150);
ctx.lineTo(50,50);
ctx.stroke();
</script>
</body>
The result of the above code
<body>
<canvas id="c" width="300" height="300"></canvas>
<script>
var c = document.getElementById('c');
var ctx = c.getContext('2d');
ctx.moveTo(50, 50);
ctx.lineTo(150,50);
ctx.lineTo(50,150);
ctx.lineTo(50,50);
ctx.stroke();
ctx.moveTo(75 ,75);
ctx.lineTo(125, 75);
ctx.lineTo(125, 125);
ctx.lineTo(75, 75);
ctx.fill();
</script>
</body>
The result of the above code
Use ctx.beginPath to clear old path and start a new one.
When you use path construction methods like, ctx.rect, ctx.arc, ctx.moveTo, ctx.lineTo, ctx.quadraticCurveTo, ctx.bezierCurveTo, ctx.ellipse, ctx.arcTo and ctx.closePath you are adding to the current path held in the 2D context memory.
When you call ctx.fill or ctx.stroke you render the current stored path. The path stored is continually added to with the 1st paragraphs path constructor methods. The path stays stored until you call ctx.beginPath which will clear the current path and start a new one.
Note that resizing the canvas will also clear the current path and reset the canvas context to its default state, though it is not recommended as a way to revert to the default state.
So to correctly render the two paths use ctx.beginPath to create a new path for each you want to render.
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
ctx.beginPath(); // begin a new path
// Note that the path is offset by 0.5
// A line is defined by its center. A line 1 pixel wide has half the line
// above and half below. If you render at the pixel boundary from 50,50 to 100,50
// half the line is rendered on the 49th row and half on the 50th row.
// Offsetting by 0.5 ensures the line is rendered on the pixels you want
// producing a better quality line.
ctx.moveTo(50.5, 10.5);
ctx.lineTo(150.5,10.5);
ctx.lineTo(50.5,110.5);
//ctx.lineTo(50,10);
ctx.closePath(); // use this as lineTo does not connect the start and end lines.
ctx.stroke();
ctx.beginPath();
ctx.moveTo(75 ,75);
ctx.lineTo(125, 75);
ctx.lineTo(125, 125);
ctx.lineTo(75, 75);
// you don't need to use closePath here as fill does not treat continuous path
// segments as special.
ctx.fill();
Note that when you use ctx.beginPath the method ctx.lineTo acts like ctx.moveTo if it is the first method called after the ctx.beginPath call. This can help reduce the complexity of rendering functions.
Correct use of ctx.closePath
It is a common mistake to think that ctx.closePath is related to ctx.beginPath. The function ctx.closePath is a path constructor function that adds to the current path by creating a line from the last added point to the last ctx.moveTo or first point after a ctx.beginPath call.
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
canvas.width = 400;
ctx.beginPath(); // begin a new path
ctx.beginPath();
ctx.moveTo(100.5, 10.5);
ctx.lineTo(150.5, 110.5);
ctx.lineTo(50.5, 110.5);
ctx.closePath(); // creates a line from (50,110) to (100,10)
ctx.moveTo(300.5, 10.5);
ctx.lineTo(350.5, 110.5);
ctx.lineTo(250.5, 110.5);
ctx.closePath(); // creates a line from (250,110) to (300,10)
ctx.stroke();
Why use ctx.closePath()
The ctx.closePath ensures that the ends of the shape are joined as a single path and will be rendered using ctx.lineJoin setting. If you don't use it then the path is open and the ctx.lineCap setting will be applied to the ends
The snippet shows the difference between ctx.closePath and using ctx.lineTo to create a line back to the start. Notice the join at the top of the triangle. The left one used ctx.closePath and the right used ctx.lineTo
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
canvas.width = 400;
ctx.lineWidth = 15;
ctx.lineJoin = "round";
ctx.lineCap = "butt";
ctx.beginPath();
ctx.moveTo(100,20);
ctx.lineTo(150,120);
ctx.lineTo(50,120);
ctx.closePath(); // creates a line from (50,210) to (100,20)
// closed path with the line joined at the start
ctx.stroke()
ctx.beginPath();
ctx.moveTo(250,20);
ctx.lineTo(300,120);
ctx.lineTo(200,120);
ctx.lineTo(250,20);
// path is open the last line back to the start is still open and
// will use ctx.lineCap to render the start and end
ctx.stroke()

Clean text from canvas shapes

How I clean text from canvas shape ?
this my code:
<div id="ways" style="width:1000px;margin:0 auto;height:100%;">
<canvas id="canvas" width="1000" height="1000"></canvas>
and fiddle
Just refactor the code a bit -
Extract the lines which draws the circle into a single function which takes one of those circle objects as an argument:
function drawCircle(circle) {
context.beginPath();
context.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI, false);
context.fillStyle = 'green';
context.fill();
context.lineWidth = lineWidth;
context.strokeStyle = '#003300';
context.stroke();
}
Then replace those lines which they came from originally, in the loop with this line (after the circle object has been pushed):
drawCircle(circles[circles.length-1]); // draw last added circle
Now you can use this function in your click events by modifying the code (here, it toggles text on and off):
if (context.isPointInPath(x, y)) {
if (circle.clicked) {
drawCircle(circle); // now we can reuse this function to clear the text
circle.clicked = false;
}
else {
circle.clicked = true;
context.fillStyle = "blue";
context.font = "bold 34px Arial";
context.textAlign="center";
context.fillText("Yeah", circle.x, circle.y);
}
break;
}
Modified fiddle
Just a note with this approach: this will redraw the circles on top of each other. Over time the lines will start to look jaggy due to the added anti-aliasing on the edges. For this reason a full clear of the canvas is better, and then redraw them all. But I'll leave that as an exercise for you.. :-)

Rotate is not working when it's called on button click Event

The rotate function is working on by default rendering, but on button click event the rotate function is not working !
<!DOCTYPE html>
<html>
<style>
body { margin: 10px;
padding: 10px;
}
</style>
<body>
<script>
function Rotatectx()
{
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.lineWidth=10;
ctx.rotate(10*Math.PI/180); //This rotate is not getting called
}
</script>
<button onclick="Rotatectx()">Rotate</button>
<div id="IDD">
<canvas id="myCanvas" width="500" height="500" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
</div>
<script>
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
//ctx.rotate(10*Math.PI/180); enable this to get a rotation.
ctx.fillStyle='#FF3399';
ctx.shadowBlur=15;
ctx.shadowOffsetX=10;
ctx.shadowOffsetY=10;
ctx.shadowColor="#009933";
ctx.translate(200,100);
var gradient=ctx.createLinearGradient(0,0,170,0);
gradient.addColorStop("0","magenta");
gradient.addColorStop("0.5","blue");
gradient.addColorStop("1.0","red");
ctx.strokeStyle=gradient;
ctx.lineWidth=5;
var grd = ctx.createRadialGradient(238, 50, 10, 238, 50, 300);
grd.addColorStop(0, '#8ED6FF');
grd.addColorStop(1, '#004CB3');
// for custom shape.
ctx.moveTo(120,20);
ctx.lineTo(30,90);
ctx.lineTo(30,130);
ctx.lineTo(120,200);
ctx.lineTo(210,130);
ctx.lineTo(210,90);
ctx.lineTo(120,20);
ctx.closePath();
ctx.fillstyle= '#8ED6FF';
ctx.fill();
ctx.stroke();
</script>
</body>
</html>
Re-factor your code a little bit. Setting rotate will only affect the next drawings.
Try something like this:
Online demo here
/// place these in global context once
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
function Rotatectx() {
ctx.clearRect(0, 0, c.width, c.height); /// clear canvas with
/// current rotation.
ctx.translate(c.width * 0.5, c.height * 0.5); /// translate to center first
ctx.rotate(10 * Math.PI/180); /// rotate
ctx.translate(-c.width * 0.5, -c.height * 0.5); /// translate back
render(); /// call the drawing
}
Unless you want the image to be rotated by its corner we need to translate to center first, then rotate and translate back. This is because rotation (transformation) matrix always rotate around origin (0,0).
Then put your drawing that you want to update in a method which makes it easy to reproduce. You need to clear the canvas each time as well - for now I put this into your rotation method as the transformation matrix also affect the clearRect() method. You might want to re-factor further by having a clear() method in your script.
function render() {
ctx.save(); /// this saves current state
ctx.beginPath(); /// important!
ctx.fillStyle='#FF3399';
ctx.shadowBlur=15;
ctx.shadowOffsetX=10;
ctx.shadowOffsetY=10;
ctx.shadowColor="#009933";
ctx.translate(200,100);
var gradient=ctx.createLinearGradient(0,0,170,0);
gradient.addColorStop("0","magenta");
gradient.addColorStop("0.5","blue");
gradient.addColorStop("1.0","red");
ctx.strokeStyle=gradient;
ctx.lineWidth=5;
var grd = ctx.createRadialGradient(238, 50, 10, 238, 50, 300);
grd.addColorStop(0, '#8ED6FF');
grd.addColorStop(1, '#004CB3');
// for custom shape.
ctx.moveTo(120,20);
ctx.lineTo(30,90);
ctx.lineTo(30,130);
ctx.lineTo(120,200);
ctx.lineTo(210,130);
ctx.lineTo(210,90);
ctx.lineTo(120,20);
ctx.closePath();
ctx.fillstyle= '#8ED6FF';
ctx.fill();
ctx.stroke();
ctx.restore(); /// this restore to previous state
}
Due to the code using a local translate, set shadow etc. a save()/restore() may be an advantage here as the restore will "reset" all these settings after the render is done. If not the added translate you have in there as well as shadow will affect the next things you draw to the canvas.
To reset the rotation you can do:
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, c.width, c.height);
ctx.render();
The context.rotate function doesn't affect anything you've drawn on the canvas before. It only affects what you draw afterwards. When you want to rotate the existing content of a canvas, you have to erase it, rotate it and then draw everything again.
Alternatively you could use CSS3 transformation in the style of the canvas-element to rotate the display of the canvas in the document. But keep in mind that CSS transformation is a non-standard feature which is still in working-draft status.

Categories

Resources