0

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);
  • I have had issue before when using `transform` and `rotate` without using `save` and `restore`. I don’t know if this is one of those times but can you link to a working example of your code? – Justin May 17 '21 at 02:53
  • To add on I’ve also seen issues when drawing and not closing the path `ctx.closePath();` after creating the arc. – Justin May 17 '21 at 02:59
  • thanks @Justin , I added the link to the working example to the question. – Carlos D. Zapata May 17 '21 at 09:36
  • I don't have time right now but real quick I noticed that when checking an item that it is complete if I console log `completedTasks` it does not increase by 1 except for the first item, the rest increase by a lot. The math for you percentage also is wrong. If I have 10 `pendingTasks` and 10 `completedTasks` I should get 100% because I've done them all. Your formula shows 50%. Also when I add a bunch of tasks and check them off nothing happens to the graph. Lastly is there supposed to be a delete button that appears? It seems that way but I have yet to see one. – Justin May 17 '21 at 13:36
  • Indeed there is work to be done regarding the functions, but I would like to know, assuming that the functions are working, and the variables are updated correctly, where and how could I call `drawRingProgress`. My logic with the variables `pendingTasks` and `completedTasks` is that if there is a group of 20 tasks on the list at a certain point, with 10 completed, then the progress is 50%. You're considering the transformation of 10 tasks from pending to complete which is of course a valid approach too. The delete button is missing but you can click the task boxes on the right. – Carlos D. Zapata May 17 '21 at 19:54

2 Answers2

1

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.

Justin
  • 2,873
  • 3
  • 9
  • 16
1

Hey Carlos and everybody interested in a solution.

After investigating the code I noticed the problem lies in creating the elements span and canvas everytime the function gets invoked but never removed.

The solution to that is to have these elements in place to begin with, namely in the html code || or create them once before the function is called.

As for the variables pendingTasks and completedTasks, I would suggest changing them to pendingTasks and totalAmountOfTasks. (Unless there is a third state in which they can be.) Then the ratio you would feed into the circle is pendingTasks/totalAmountOfTasks.

Remember to check for dividing by zero, when there are no tasks!

Cheers,

Thomas

Broken_Window
  • 2,037
  • 3
  • 21
  • 47
ThomasBeck
  • 11
  • 1