Dynamic SVG circles with timer and progress animation

Is there a way to modify the following:

var el = document.getElementById('graph'); // get canvas

var options = {
    percent:  el.getAttribute('data-percent') || 25,
    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); // adjust 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) / 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('#555555', options.lineWidth, options.percent / 100);
div {
    position:relative;
    margin:80px;
    width:220px; height:220px;
}
canvas {
    display: block;
    position:absolute;
    top:0;
    left:0;
}
span {
    color:#555;
    display:block;
    line-height:220px;
    text-align:center;
    width:220px;
    font-family:sans-serif;
    font-size:40px;
    font-weight:100;
    margin-left:5px;
}

input {
    width: 200px;
}
<div class="chart" id="graph" data-percent="10"></div>

To resemble this:

https://i.sstatic.net/0axQs.jpg

The outer circle represents progress from 0 to 100%
The inner circle indicates time remaining

I managed to achieve this but it's not quite perfect http://codepen.io/di3orlive/pen/wKjBzY Seeking to enhance the design perhaps with an added arrow

Answer №1

let element = document.getElementById('graph'); // retrieving canvas element

let options = {
  percent: element.getAttribute('data-percent') || 25,
  size: element.getAttribute('data-size') || 220,
  lineWidth: element.getAttribute('data-line') || 4,
  rotate: element.getAttribute('data-rotate') || 0
}

function Gauge() {
  this.to_rad = to_rad = Math.PI / 180;
  this.percentBg = "#efefef";
  this.percentFg = "#555555";
  this.tickFg = "#cccccc";
  this.tickBg = "transparent";
  this.tickFill = true;
  this.tickDirection = -1; 
  this.percent = 0;
  this.size = 220;
  this.lineWidth = 14;
  this.rotate = 0;
  this.ticks = 40;
  this.tick = 0;
  this.canvas = document.createElement('canvas');
  this.context = this.canvas.getContext('2d');
}


Gauge.prototype.drawCircle = function(color, lineWidth, percent, drawArrow) {
  let ctx = this.context;
  let circleMargin = 10; 
  let radius = (this.size - this.lineWidth - circleMargin) / 2;

  ctx.save();
  let middle = this.size / 2;
  ctx.translate(middle, middle);
  ctx.rotate(-90 * to_rad);
  percent = Math.min(Math.max(0, percent || 1), 1);
  ctx.beginPath();
  let endRadians = 360 * percent * this.to_rad;
  ctx.arc(0, 0, radius, 0, endRadians, false);
  ctx.strokeStyle = ctx.filStyle = color;
  ctx.lineCap = 'round'; 
  ctx.lineWidth = lineWidth;
  ctx.stroke();

  if (drawArrow === true && percent !== 1) {
    ctx.beginPath();
    ctx.rotate(endRadians);

    let arrowWidth = this.lineWidth + 12;
    let arrowHeight = 10;
    ctx.moveTo(radius - (arrowWidth / 2), 0);
    ctx.lineTo(radius + (arrowWidth / 2), 0);
    ctx.lineTo(radius, arrowHeight);
    ctx.fill();
  }

  ctx.restore();
};

Gauge.prototype.drawTicks = function() {
  let circleMargin = 10; 
  let radius = (this.size - this.lineWidth - circleMargin) / 2;
  let ctx = this.context;

  ctx.save();
  let mid = this.size / 2;
  ctx.translate(mid, mid);
  ctx.rotate(-90 * to_rad);
  ctx.lineWidth = 3;

  let angle = 360 / this.ticks;
  let tickSize = 20;
  let tickMargin = 10;

  for (let i = 0; i < this.ticks; i++) {
    if ((i < this.tick && this.tickFill == true) || i == this.tick) {
      ctx.strokeStyle = this.tickFg;
    } else {
      ctx.strokeStyle = this.tickBg;
    }
    ctx.save();
    if (this.tickDirection === -1) {
      ctx.rotate((360 - (i * angle)) * this.to_rad);
    } else {
      ctx.rotate(i * angle * this.to_rad);
    }
    ctx.beginPath();
    ctx.moveTo(radius - tickSize - tickMargin, 0);
    ctx.lineTo(radius - tickMargin, 0);
    ctx.stroke();
    ctx.restore();
  }
  ctx.restore();
};

Gauge.prototype.render = function(el) {
  this.canvas.width = this.can.height = this.size;
  this.span = document.createElement('span');

  el.innerHTML = "";
  el.appendChild(this.canvas);
  el.appendChild(this.span);

  let self = this;
  let ctx = self.context;

  function repeat() {

    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    self.drawCircle(self.percentBg, self.lineWidth, 100 / 100);
    self.drawCircle(self.percentFg, self.lineWidth, self.percent / 100, true);
    self.drawTicks();
    self.timeout = setTimeout(function() {
      repeat()
    }, 1000 / 30);
    
  }

  repeat();
}

let myGauge = new Gauge();

myGauge.size = options.size;
myGauge.percent = options.percent;
myGauge.lineWidth = options.lineWidth;
myGauge.percent = options.percent;
myGauge.render(element)

let myGauge2 = new Gauge();

myGauge2.size = options.size;
myGauge2.percent = options.percent;
myGauge2.lineWidth = options.lineWidth;
myGauge2.percent = options.percent;
myGauge2.tickFg = "#FF8800";
myGauge2.tickBg = "#EEEEEE";
myGauge2.tickFill = false;
myGauge2.ticks = 60;
myGauge2.tickDirection = 1;
myGauge2.render(document.getElementById('gauge'));

let startTime = (new Date()).getTime();

let countDown = 99;

function dataLoop() {


  myGauge.percent = myGauge.percent > 100 ? 100 : (myGauge.percent * 1) + .1;

  let elapsedMs = (new Date().getTime()) - startTime; 
  let elapsedSec = elapsedMs / 1000;
  let remainSec = Math.floor(countDown - elapsedSec);  
  let progress = remainSec <=0 ? 1 : elapsedSec / countDown;
  myGauge.tick = Math.floor(progress * myGauge.ticks); 
  myGauge.span.innerHTML = remainSec > 0 ? remainSec + " sec" : "---";
  
  let d = new Date();

  myGauge2.percent = (d.getMinutes() / 60) * 100;
  if (myGauge2.percent > 100) myGauge2.percent = 100;
  myGauge2.tick = d.getSeconds();
  myGauge2.span.innerHTML = d.getSeconds() + " sec";
  setTimeout(dataLoop,1000/30);
}

dataLoop();
div {
  position: relative;
  margin: 80px;
  width: 220px;
  height: 220px;
}
canvas {
  display: block;
  position: absolute;
  top: 0;
  left: 0;
}
span {
  color: #555;
  display: block;
  line-height: 220px;
  text-align: center;
  width: 100%;
  font-family: sans-serif;
  font-size: 40px;
  font-weight: 100;
  margin-left: 5px;
}
input {
  width: 200px;
}
<table>
  <tr>
    <td>
      <div class="chart" id="graph" data-percent="10"></div>
    </td>
    <td>
      <div id="gauge"></div>
    </td>
  </tr>
</table>

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

Looking to create dynamic pages on NextJS without relying on fixed paths?

I am looking to create a unique user experience by offering discounts on my website based on the users' citizenship ID numbers. By using their ID number, I can customize the discount amount according to factors such as location, age, and gender. User ...

The Angular Http Interceptor is failing to trigger a new request after refreshing the token

In my project, I implemented an HTTP interceptor that manages access token refreshing. If a user's access token expires and the request receives a 401 error, this function is designed to handle the situation by refreshing the token and re-executing ...

Using a timer to make Ajax calls twice on a single webpage

Can multiple ajax calls be made simultaneously on the same page to different receiving div tags? I am struggling to find a solution to this issue. I have a total of 3 pages: a home page, and two PHP pages named status and alert which echo the results. Wi ...

Issue with AJAX Complete event not functioning

Currently, I am facing an issue with firing the .ajaxComplete function on a demo site. I have referred to this function from this link. Below is my code : <SCRIPT type="text/javascript"> <!-- /* Credits: Bit Repository Source: http://www.bit ...

The webpage continues to refresh after executing a JavaScript function triggered by the AJAX response

I have experimented with various solutions for calling a JavaScript function returned from an AJAX response. While each method worked to some extent, I found that using an alert within the refreshResults function was necessary in order to display the resul ...

Using Three.js to display a CSS3D-rendered DIV in full-screen mode

After extensively experimenting with the three.js library, I was able to establish two distinct scenes – one canvas-rendered and the other css3d-rendered – both displayed using the same perspective camera. Note: Despite my efforts, I am constrained to ...

select specific region within image

I'm currently working on cropping an image and sending the cropped data to the server side. To achieve this, I am utilizing the imgareaselect plugin. While I am able to obtain the coordinates of the selection, I am facing challenges in actually croppi ...

How can we determine in JavaScript whether a certain parameter constitutes a square number?

I've developed a function that can determine whether a given parameter is a square number or not. For more information on square numbers, check out this link: https://en.wikipedia.org/?title=Square_number If the input is a square number, it will ret ...

Getting an Array of all values in <th> using jQuery

Is there a more efficient way in jQuery to retrieve an array of all the inner texts of <th> elements within a table? The following code snippet successfully achieves this: $("th").toArray().map(th => th.innerText) I'm curious if there is a ...

Is there a way I can utilize a for-loop and if statement in JavaScript to present the information accurately within the table?

My current task involves fetching data via AJAX and then using a for-loop and if-statement to determine which goods belong in each shopping cart. Once identified, I need to display these goods in separate tables corresponding to each customer. Although the ...

Create a custom hook that encapsulates the useQuery function from tRPC and provides accurate TypeScript typings

I have integrated tRPC into a project that already has API calls, and I am looking to create a separate wrapper for the useQuery function. However, I am facing challenges in getting the TypeScript types right for this customization. My Objective This is w ...

Here is a unique version: "Dealing with Node.js ES6 (ESM) Modules in TypeScript can be tricky, especially when the TypeScript Compiler (TSC) fails to emit the

I am facing an issue while transpiling my TypeScript project to JavaScript. I have set the project to resolve as an ES6 Module (ESM) by using the "module":"ES6" configuration, but the problem persists. This is the current setup in my ...

Creating Image Overlays with Hover Effects using CSS and PHP Conditions

Looking for some assistance with a Minecraft server listing page I am working on for a client. I have never done this before and have encountered a roadblock. Take a look at this image (apologies for using Paint, I was in a hurry!) The goal is to have the ...

Calculator app with Next.js: Using keyboard input resets the current number state in real-time

While developing a basic calculator application with Next.js, I encountered an issue. The functionality works correctly when the user interacts with the HTML buttons, but when trying to input numbers using the keyboard, the state resets to 0 before display ...

Select items from object based on a specified array of IDs

I am facing a challenge with my list of objects that each have an array associated with them. For instance, take a look at this example: "-KpvPH2_SDssxZ573OvM" : { "date" : "2017-07-25T20:21:13.572Z", "description" : "Test", "id" : [ { ...

Setting `tabBarVisible` to false does not function properly within a stackNavigation nested element

Details on my project dependencies: "react-navigation": "^3.6.0", "expo": "^32.0.0" I'm working with a TabNavigator that contains redirections to child components, which are StackNavigators. However, I'm facing an issue where I am unable to hide ...

I'm looking to create a parent div that adjusts its size dynamically and can contain two child divs, each with variable sizes that can scroll independently. How can I achieve this?

Consider this layout configuration: <div id="PARENT_DIV"> <div id="LEFT_CHILD_DIV"> </div> <div id="RIGHT_CHILD_DIV"> </div> </div> Key features for PARENT_DIV: PARENT_DIV must have a higher z-ind ...

What is the best way to create a full bleed background image that adjusts to different screen resolutions using CSS and JavaScript?

Similar Question: Full Screen Background Image in Firefox Check out: Is there a way to achieve a similar effect on a website where the content adjusts to different monitor resolutions? On the Ingress site, it seems like everything scales proportional ...

"Usage of Wildcard in CSS Selectors for customizable path selection

When it comes to identifying elements using CSS selectors, I rely on a combination of path and attribute-based selectors for accurate results. For instance: div[attribute='test'] > div > div > div > span[attribute='test'] ...

Navigating the complexities of extracting and storing a data type from a collection of objects

Dealing with a messy API that returns inconsistent values is quite challenging. Instead of manually creating types for each entry, I am looking for a way to infer the types programmatically. One approach could be by analyzing an array like this: const arr ...