Enhance your SVG progress circle by simply selecting checkboxes

I have a unique system with 5 checkboxes that act as a To-Do list. When I click on each checkbox, the circle's diameter should progressively fill up in increments of 1/5 until all 5 checkboxes are completed. The order in which the checkboxes are clicked should not affect the progress. For example, if I click on the "first ToDo" and "third ToDo" without selecting the "second ToDo", the circle should show 2/5 completion.

Currently, I have successfully made the circle interact with the checkbox inputs, but I am facing challenges in visually representing the progress accurately. When clicking on a ToDo item, the circle marks the progress but does not reflect the correct order or proportions. My goal is to make the visual progress move forward towards completion whenever a ToDo item is checked off.

In my search for solutions online, I have come across mentions of using <circle path>. However, this has been confusing for me as some configurations involve CSS while others use JavaScript. In my opinion, it would be best to utilize JavaScript to display the progress, as it needs to respond based on the status of the checkboxes.

function update() {

  var myBar = document.getElementById("myCircle");
  //Reference the Form.
  var toDo = document.getElementById("toDo");

  //Reference all the CheckBoxes in Table.
  boxes = toDo.querySelectorAll("input[type='checkbox']:checked");
  checked = boxes.length


  myBar.style.strokeDasharray = ((checked) * 100) + "%";
  if (checked === 0) {
    alert("Please select CheckBox(s).");
  }
  return true;
}

checks = document.querySelectorAll("input[type='checkbox']");
checks.forEach(function(box) {
  box.addEventListener("change", function() {
    update()
  });
});
#myCircle {
  width: 0;
  height: 30px;
  stroke: #A3BBAD;
  stroke-width: 3px;
}

#myProgress {
  margin: auto;
  width: 50%;
  stroke: #357266;
  stroke-width: 3px;
}
<svg height="100" width="100">
  <circle id="myProgress" cx="50" cy="50" r="40" fill="transparent"/>
  <circle id="myCircle" cx="50" cy="50" r="40" fill="transparent"/>
</svg>
<br>

<form id="toDo">
  <input id="FirstToDo" type="checkbox" value="1" /><label for="FirstToDo">First</label>
  <input id="SecondToDo" type="checkbox" value="2" /><label for="SecondToDo">Second</label>
  <input id="ThirdToDo" type="checkbox" value="3" /><label for="ThirdToDo">Third</label>
  <input id="FourthToDo" type="checkbox" value="4" /><label for="FourthToDo">Fourth</label>
  <input id="FifthToDo" type="checkbox" value="5" /><label for="FifthToDo">Fifth</label>
</form>
<script src="Circle.js"></script>

Answer №1

To begin, it's important to grasp that the percent unit does not represent a fraction of the circle's path. In this scenario:

When a percentage is utilized, it denotes a percentage of the current viewport.
[ source w3.org emphasis mine ]

If you want to specify a total length for the circle's path, you can utilize pathLength="100" (refer to MDN for more details on pathLength).
Afterwards, adjust your JS code to set the strokeDasharray value based on 100 units for the circle's path length.
Here is one potential method:

function update() {

  var myBar = document.getElementById("myCircle");
  //Access the Form.
  var toDo = document.getElementById("toDo");

  //Retrieve all CheckBoxes within the Table.
  boxes = toDo.querySelectorAll("input[type='checkbox']:checked");
  checked = boxes.length


  myBar.style.strokeDasharray = (100 - (checked) * 20) + ', 100';
  if (checked === 0) {
    alert("Please select CheckBox(s).");
  }
  return true;
}

checks = document.querySelectorAll("input[type='checkbox']");
checks.forEach(function(box) {
  box.addEventListener("change", function() {
    update()
  });
});
svg {
  transform: rotate(-90deg);
}
#myCircle {
  width: 0;
  height: 30px;
  stroke: #A3BBAD;
  stroke-width: 3px;
}

#myProgress {
  margin: auto;
  width: 50%;
  stroke: #357266;
  stroke-width: 3px;
}
<svg height="100" width="100">
  <circle id="myProgress" cx="50" cy="50" r="40" fill="transparent"/>
  <circle id="myCircle" cx="50" cy="50" r="40" fill="transparent" pathLength="100"/>
</svg>
<br>

<form id="toDo">
  <input id="FirstToDo" type="checkbox" value="1" /><label for="FirstToDo">First</label>
  <input id="SecondToDo" type="checkbox" value="2" /><label for="SecondToDo">Second</label>
  <input id="ThirdToDo" type="checkbox" value="3" /><label for="ThirdToDo">Third</label>
  <input id="FourthToDo" type="checkbox" value="4" /><label for="FourthToDo">Fourth</label>
  <input id="FifthToDo" type="checkbox" value="5" /><label for="FifthToDo">Fifth</label>
</form>

Note: The circle stroke defaults to starting on the right side of the circle, so I included a rotation (-90deg) in the svg to ensure the progress bar initiates at the top of the circle.

Answer №2

Here's a clever CSS-only solution I came up with. In this approach, the input boxes need to be placed above the circle in the HTML structure. To make it work seamlessly, I employed a layout trick, but you can always fine-tune it using JavaScript while keeping the original markup intact.

The magic lies in a conic gradient that depends on the values of 5 CSS variables controlled by checkboxes. If more customization is required, JS can also handle this task effectively. The crucial aspect here is coloring an area based on a dynamic sum rather than coloring fixed segments.

#myProgress {
  --task1: 0;
  --task2: 0;
  --task3: 0;
  --task4: 0;
  --task5: 0;
}

#FirstToDo:checked ~ #myProgress {
  --task1: 1;
}

#SecondToDo:checked ~ #myProgress {
  --task2: 1;
}

#ThirdToDo:checked ~ #myProgress {
  --task3: 1;
}

#FourthToDo:checked ~ #myProgress {
  --task4: 1;
}

#FifthToDo:checked ~ #myProgress {
  --task5: 1;
}

.flex {
  display: flex;
  flex-direction: row;
}

.circle {
  order: -1;
  width: 100px;
  height: 100px;
  background: blue;
  border-radius: 50%;
  mask: radial-gradient(farthest-side, transparent 0 70%, #fff 70% 100%);
  -webkit-mask: radial-gradient(farthest-side, transparent 0 70%, #fff 70% 100%);
}   

#myProgress {
  background-image: conic-gradient(
    green 0 calc((var(--task1) + var(--task2) + var(--task3) + var(--task4) + var(--task5))* 20%),
    transparent 0 100%);
}
<div class="flex">
    <input id="FirstToDo" type="checkbox" value="1" /><label for="FirstToDo">First</label>
    <input id="SecondToDo" type="checkbox" value="2" /><label for="SecondToDo">Second</label>
    <input id="ThirdToDo" type="checkbox" value="3" /><label for="ThirdToDo">Third</label>
    <input id="FourthToDo" type="checkbox" value="4" /><label for="FourthToDo">Fourth</label>
    <input id="FifthToDo" type="checkbox" value="5" /><label for="FifthToDo">Fifth</label>

  <div id="myProgress" class="circle"></div>
</div>

Answer №3

Here are some steps you can follow:

  • Include pathLength="100" on the circle tag to specify that the percentage circle should take up 100% of the circle path

  • The calculation for percentage will be 100 - percent

  • You can adjust the percentage using the CSS property strokeDashoffset like this:

     myBar.style.strokeDashoffset = 100 - percent;
    

    function update() {

      var myBar = document.getElementById("myProgress");
      //Reference the Form.
      var toDo = document.getElementById("toDo");

      //Reference all the CheckBoxes in Table.
      boxes = toDo.querySelectorAll("input[type='checkbox']:checked");
      checked = boxes.length

      var percent = (checked) * 20;

      myBar.style.strokeDashoffset = 100 - percent;
      if (checked === 0) {
        alert("Please select CheckBox(s).");
      }
      return true;
    }

    checks = document.querySelectorAll("input[type='checkbox']");
    checks.forEach(function(box) {
      box.addEventListener("change", function() {
        update()
      });
    });
   #mySvg {
      transform: rotate(-90deg);
   }
   
   #myCircle {
      ...
    }
    <!DOCTYPE html>
    <html lang="en">

    <head>
      <meta charset="UTF-8>
      <title>Circle</title>
      <link rel="stylesheet" href="style.css">
    </head>

    <body>

      <svg id="mySvg" height="100" width="100">
        <circle id="myCircle" cx="50" cy="50" r="40" fill="transparent"/>
        <circle id="myProgress" cx="50" cy="50" r="40" fill="transparent" pathLength="100"/>
      </svg>
      <br>

      <form id="toDo">
        <input id="FirstToDo" type="checkbox" value="1" /><label for="FirstToDo">First</label>
        <input id="SecondToDo" type="checkbox" value="2" /><label for="SecondToDo">Second</label>
        <input id="ThirdToDo" type="checkbox" value="3" /><label for="ThirdToDo">Third</label>
        <input id="FourthToDo" type="checkbox" value="4" /><label for="FourthToDo">Fourth</label>
        <input id="FifthToDo" type="checkbox" value="5" /><label for="FifthToDo">Fifth</label>
      </form>
      <script src="Circle.js"></script>
    </body>

    </html>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Inject a <div> element into a table row upon clicking a button using jQuery and PHP

I am faced with an issue regarding displaying pdf reports within specific table rows. The scenario involves a table containing folder paths and buttons in each row. Upon clicking the button, the path is sent to a sequence of PHP files (ajax.php, ajax2.php, ...

Creating a route-guard in a Vue.js/Firebase authentication system for secure navigation

I have created a Vue.js/Firebase authentication interface following a tutorial. The app is mostly functioning properly, but I am experiencing issues with the route guard. Upon signing in and redirecting from "http://localhost:8080/home" to "http://localh ...

GraphQL/Relay Schema "Field cannot be queried..."

I'm encountering an issue when trying to query specific fields in graphql/relay. My goal is to retrieve the "courseName" field for Courses from the schema provided below. For instance, if I query the User with: { user(id:1){firstname,lastname}} T ...

Customize dynamically loaded data via AJAX

I have a webpage that is loading a table from another source. The CSS is working fine, but I am facing an issue editing the table using jQuery when it's loaded dynamically through a script. It seems like my changes are not getting applied because they ...

The module '@algolia/cache-common' is missing and cannot be located

summary: code works locally but not in lambda. My AWS lambda function runs perfectly when tested locally, utilizing Algolia within a service in the server. Despite installing @algolia/cache-common, any call to the lambda results in a crash due to the erro ...

I'm encountering problems when attempting to display the image/png response from an xmlHTTPRequest. Instead of the correct data, I

I have implemented the following code to integrate a captcha generating web service. The response data is successfully obtained, but when I attempt to display the result within a div element, the image appears as distorted text. var xmlHttp = new XMLHtt ...

Creating a dynamic JSON structure from an array list of elements: A step-by-step guide

I am faced with the task of transforming an array of employee IDs, listed as follows: empIds: [38670, 38671, 38672, 38673], into a JSON payload structured like this: { "members": [ { "EmployeeId": &quo ...

Enhancing user experience with VideoJS player overlay buttons on mobile devices

I am currently using VideoJs player version 4.2. When I launch a videojs player on Safari browser in an iOS device, it defaults to native controls. However, when I pause the player, overlay buttons (links to navigate to other pages) are displayed on the v ...

Arrange the columns in Angular Material Table in various directions

Is there a way to sort all columns in an Angular material table by descending order, while keeping the active column sorted in ascending order? I have been trying to achieve this using the code below: @ViewChild(MatSort) sort: MatSort; <table matSort ...

Dynamically loading an iFrame source and automatically populating input forms using Javascript explained

My current challenge involves retrieving URL parameters successfully and using them to decide which iframe src to populate. Additionally, I need to autofill the form created via the form src with other parameters. However, I am faced with two main issues. ...

Ways to expose a components prop to its slotted elements

I've set up my structure as follows: <childs> <child> <ul> <li v-for="item in currentData">@{{ item.name }}</li> </ul> </child> </childs> Within the child component, t ...

Angular: Struggling with Parent Component not recognizing changes in Child Component

Currently, I am facing an issue with my shopping cart functionality. When the website first loads and a user does not have a saved shopping cart, they need to click "add to cart" before one is created. The problem lies in the fact that my app.component doe ...

How to retrieve the user-agent using getStaticProps in NextJS

Looking to retrieve the user-agent within getStaticProps for logging purposes In our project, we are implementing access and error logs. As part of this, we want to include the user-agent in the logs as well. To achieve this, we have decided to use getSta ...

Choose the number that is nearest to the options given in the list

I am faced with a challenge involving a list of numbers and an input form where users can enter any number, which I want to automatically convert to the closest number from my list. My list includes random numbers such as 1, 5, 10, 12, 19, 23, 100, 400, 9 ...

Include a description in the cell of the table

I am struggling with adding a small description below one of the columns in my three-column table. I have tried using Grid, but so far, nothing has worked for me. Can anyone provide assistance? To give you a better idea, I have highlighted the desired res ...

Utilizing the NestJS Reflector within a Custom Decorator: A Comprehensive Guide

I have implemented a solution where I use @SetMetaData('version', 'v2') to specify the version for an HTTP method in a controller. Additionally, I created a custom @Get() decorator that appends the version as a suffix to the controller ...

Issues with APIs surfaced following the transition from DataGridPro to DataGridPremium

In my current project, we initially implemented the MUI X DataGrid but later switched to DataGridPro due to business requirements. Recently, we upgraded our plan from Pro to Premium which has caused some unexpected issues in our existing code. I am using " ...

Having trouble accessing dynamically generated elements using Selenium

I've been attempting to change the router's SSIDs using a Selenium script, but I'm encountering difficulty accessing any JS elements generated by the router page. I've tried various Expected Conditions and methods without success. Here ...

Loading an Angular2 app is made possible by ensuring that it is only initiated when a DOM element is detected

In my main.ts file, the code below is functioning perfectly: import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule); H ...

The download attribute in HTML5 is functioning properly, however it is causing a redirection to

Is it possible to use the HTML5 download attribute with a URL without being redirected to a new page? Here is the code I am currently using: exportToPdf: function(e) { var link = document.createElement('a'); link.href = 'https://filegener ...