I'm building a to-do list with a progress circle, using one of the alternatives given here (CSS Progress Circle). In my script.js I defined the function drawRingProgress()
which renders the canvas when I execute it at the end of the script.
As the other functions of my script are executed to add tasks, edit, remove, or mark them as complete, the parameters pendingTasks
and completedTasks
get updated. However, if I call the function drawRingProgress()
within the other mentioned functions, in order to update the progress, the canvas is wrongly drawn somewhere else multiple times (depending on the HTML elements these functions are acting on). What would be a correct approach to render the updated progress percentage?
Link to the working example: https://jsfiddle.net/tailslider13/f4qtmhzj/7/
let pendingTasks = 31;
let completedTasks = 69;
function drawRingProgress(pendingTasks, completedTasks) {
var el = document.getElementById('graph'); // get canvas
let progress_percentage = Math.floor((completedTasks / (completedTasks + pendingTasks)) * 100) || 0;
var options = {
// percent: el.getAttribute('data-percent') || 25,
percent: progress_percentage,
// size: 110,
size: el.getAttribute('data-size') || 220,
lineWidth: el.getAttribute('data-line') || 15,
rotate: el.getAttribute('data-rotate') || 0
}
var canvas = document.createElement('canvas');
var span = document.createElement('span');
span.textContent = options.percent + '%';
if (typeof (G_vmlCanvasManager) !== 'undefined') {
G_vmlCanvasManager.initElement(canvas);
}
var ctx = canvas.getContext('2d');
canvas.width = canvas.height = options.size;
el.appendChild(span);
el.appendChild(canvas);
ctx.translate(options.size / 2, options.size / 2); // change center
ctx.rotate((-1 / 2 + options.rotate / 180) * Math.PI); // rotate -90 deg
//imd = ctx.getImageData(0, 0, 240, 240);
var radius = (options.size - options.lineWidth) / 3.2;
var drawCircle = function (color, lineWidth, percent) {
percent = Math.min(Math.max(0, percent || 1), 1);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
ctx.strokeStyle = color;
ctx.lineCap = 'round'; // butt, round or square
ctx.lineWidth = lineWidth
ctx.stroke();
};
drawCircle('#efefef', options.lineWidth, 100 / 100);
drawCircle('#046582', options.lineWidth, options.percent / 100)
}
drawRingProgress(pendingTasks, completedTasks);
Here is how I would draw the graph. I have removed all of the other functions from this so it is only showing the graph progress based on what you set the variables to. Once you get your other functions figured out you can updated them via that method.
First I would get the canvas at the beginning of the script and also designate the variables a global.
Second I would draw the white doughnut flat out. Unless you plan on changing it in some way the function drawGraph()
will get called once and that's it.
Third the function drawRingProgress()
will get called from your other functions when you add, delete, or complete a task. Be sure those function also update pendingTasks
and completedTasks
prior to calling drawRingProgress()
.
Inside drawRingProgress()
I added the text since canvas has that built in method so you don't need to use a <span>
. As far as all your options I removed them for this but you can add them back as where you see fit.
const inputField = document.getElementById("addTask");
const taskList = document.getElementById("taskList");
var canvas = document.getElementById('graph');
var ctx = canvas.getContext('2d');
canvas.width = 200;
canvas.height = 200;
let pendingTasks = 20;
let completedTasks = 5;
//Progress ring
function drawGraph() {
ctx.beginPath();
ctx.strokeStyle = "white";
ctx.arc(canvas.width/2, canvas.height/2, 50, 0, Math.PI*2, false);
ctx.lineCap = 'round'; // butt, round or square
ctx.lineWidth = 15;
ctx.stroke();
ctx.closePath();
}
drawGraph();
function drawRingProgress(pendingTasks, completedTasks) {
let progress_percentage = (completedTasks / pendingTasks) * 100;
ctx.font = "30px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "#046582";
ctx.fillText(progress_percentage+'%', canvas.width/2,canvas.height/2);
percent = Math.min(Math.max(0, (progress_percentage/100) || 1), 1);
ctx.beginPath();
ctx.save();
ctx.translate(0, canvas.height); // change center
ctx.rotate((-1 / 2 + 0 / 180) * Math.PI); // rotate -90 deg
ctx.arc(canvas.width/2, canvas.height/2, 50, 0, Math.PI * 2 * percent, false);
ctx.strokeStyle = "#046582";
ctx.lineCap = 'round'; // butt, round or square
ctx.lineWidth = 15;
ctx.stroke();
ctx.restore();
ctx.closePath();
}
drawRingProgress(pendingTasks, completedTasks);
#body {
background-color: #046582;
}
header {
background-color: #f39189;
padding: 50px;
margin: 50px;
position: sticky;
top: 0px;
}
h1 {
text-align: center;
}
.listItem {
margin: 20px 0px;
background-color: white;
}
.container {
background-color: #c4c4c4;
}
.taskList {
list-style-type: none;
background-color: transparent;
overflow: hidden;
margin-top: 150px;
}
.inputContainer {
margin: 50px;
padding: 20px;
max-width: 50%;
width: 100%;
background-color: #f39189;
}
#footer {
text-align: center;
position: sticky;
bottom: 0px;
background-color: #f39189;
padding: 20px;
}
.deleteButton {
background-image: url("/content/delete.png");
background-repeat: no-repeat;
background-size: cover;
cursor: pointer;
width: 30px;
height: 30px;
margin: 15px;
}
#addTask {
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 1.3rem;
}
.taskName {
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 1.2rem;
}
.listContainer {
height: 1080px;
}
.inputContainer {
position: fixed;
}
.checkedbox {
text-decoration: line-through;
color: #f39189;
}
/* START Styling Progress ring */
.chart {
position: relative;
/* margin:0px; */
width: 220px;
height: 220px;
}
canvas {
display: block;
position: absolute;
top: 0;
left: 0;
}
span {
color: #046582;
display: block;
line-height: 220px;
text-align: center;
width: 220px;
font-family: sans-serif;
font-size: 30px;
font-weight: 100;
margin-left: 5px;
}
/* Links progress ring */
/* https://stackoverflow.com/questions/14222138/css-progress-circle
http://jsfiddle.net/Aapn8/3410/ */
/* END Styling Progress ring */
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<!-- Required meta tags -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0" crossorigin="anonymous" />
</head>
<div class="container">
<body id="body">
<header class="row justify-content-end">
<h1 class="col-4">Take note</h1>
<!-- Progress ring -->
<div class="col-4">
<canvas class="chart" id="graph"></canvas>
</div>
</header>
<!-- Input field and button -->
<div class="row inputContainer rounded">
<input class="col-auto" type="newTask" placeholder="Enter new Task" id="addTask" />
<button class="col-auto" id="btnAdd">Add</button>
</div>
<!-- List of tasks created -->
<div class="listContainer">
<ul class="taskList rounded" id="taskList"></ul>
</div>
<footer class="row" id="footer">
<h6 class="col w-100">2021</h6>
</footer>
<!-- BOOTSTRAP -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous">
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.min.js" integrity="sha384-lpyLfhYuitXl2zRZ5Bn2fqnhNAKOAaM/0Kr9laMspuaMiZfGmfwRNFh8HlMy49eQ" crossorigin="anonymous">
</script>
</body>
</div>
</html>
I also wasn't sure what you intent was with using bootstraps chart. I haven't used it before but from checking the docs it didn't appear you were actually coding appropriatly for it. Also you had a <div>
with the class of chart and not a <canvas>
which appeared wrong to me (but like I said I haven't used it before). In the example here I changed it to <canvas>
and also got rid of the canvas you were creating along with the span.
Hopefully this is what you wanted if not maybe you can still piece together what I have here with what exactly you want.