Having trouble with SVG Circles - Attempting to create a Speedometer design

Trying to implement a speedometer in one of the components of my Vue project, but encountering an issue. When I input 0 into my progress calculation for determining the stroke fill, it overlaps with the background circle instead of staying within its boundaries. To provide better clarity, I have attached Screenshots & Code.

Desired outcome when passing 0 as fillLevel

Actual result observed

Illustration of fully filled scenario - For better understanding of the problem

The goal is to prevent the orange circle from exceeding the boundary of the grey circle shown in the first screenshot. The orange circle should maintain the same length even when the fillLevel is set to 0.

This is the code snippet:

<script setup lang="ts">
import { ref, onMounted, watch } from 'vue';

const fillLevel = ref(0); // max value = 360
const circleRef = ref<SVGCircleElement | null>(null);
const backgroundCircleRef = ref<SVGCircleElement | null>(null);

const circleLength = ref(0);
const strokeDashoffset = ref(0);

const calculate = () => {
  if (circleRef.value && backgroundCircleRef.value) {
    circleLength.value = backgroundCircleRef.value.getTotalLength() * 0.8;
    strokeDashoffset.value = circleLength.value * (1 - fillLevel.value / 360);
  }
}

onMounted(calculate);
watch(fillLevel, calculate);
</script>

<template>
  <div class="speedo-div">
    <svg style="width: 24vh; height: 23vh;" viewBox="0 0 271 265" fill="none" xmlns="http://www.w3.org/2000/svg">
      <!-- Some overlay content visible in the screenshots -->
    </svg>
    <div class="speedo">
      <svg class="speedo-svg" viewBox="0 0 240 230" transform="rotate(125.5)">
        <circle ref="backgroundCircleRef" class="circle" stroke="#53535399" r="35%" cy="50%" cx="50%" :stroke-dasharray="circleLength" />
        <circle ref="circleRef" class="circle active" stroke="var(--primary)" r="35%" cy="50%" cx="50%" :stroke-dashoffset="strokeDashoffset" :stroke-dasharray="circleLength" />
      </svg>
    </div>
  </div>
</template>

<style scoped>
.speedo {
  position: absolute;
  width: 24vh;
  height: 23vh;
  bottom: 0;
}

.speedo-svg {
  position: absolute;
  left: 0;
  right: 0;
  width: 24vh;
  height: 23vh;
}

.speedo-svg .circle {
  stroke-width: 25px;
  opacity: .8;
  fill: none;
  transition: stroke-dashoffset .2s linear;
}

.speedo-svg .circle.active {
  filter: drop-shadow(var(--primary-shadow));
}
</style>

Appreciate your attention!

Seeking assistance from someone experienced in SVG implementation. Please refrain from criticizing the use of vh for width as it's a personal preference.

Answer №1

Ensure that the stroke-dasharray pattern does not repeat within the visible area. Currently, the pattern consists of a dash that covers 80% of the total length, followed by an equal-length gap. When fillLevel is set to 0, the pattern is shifted by that length, causing it to begin directly with the gap. As the length reaches 80%, the gap ends and the next dash begins - resulting in the red bar you are observing.

To make the entire circle empty, the gap should span the entire circumference:

const calculate = () => {
  if (!backgroundCircleRef.value) {
    return
  }
  const fullCircleLength = backgroundCircleRef.value.getTotalLength()
  const dashLength = fullCircleLength * 0.8
  speedoLength.value = dashLength + ' ' + fullCircleLength // <---- gap encompasses full circle

  strokeDashoffset.value = dashLength * (1 - fillLevel.value / 360)
}

Now, starting at the end of the dash, the gap spans the entire circle.

Here is a playground where you can test this setup.

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

Angular router for displaying multiple view groups

What is the most effective strategy for handling two groups of views in Angular? Let me explain how I typically structure my layout in app.component.html: <app-header></app-header> <router-outlet></router-outlet> <app-footer> ...

There seems to be an issue with Firebase authentication on firebase-admin in node.js. Your client is being denied permission to access the URL "system.gserviceaccount.com" from the server

Issue I've been utilizing Firebase auth on my client and using firebase-admin to verify on the server. It was functioning well until I decided to migrate to a different server, which caused it to stop working. The crucial part of the error message i ...

Encountering issue with Konva/Vue-Konva: receiving a TypeError at client.js line 227 stating that Konva.Layer is not a

I am integrating Konva/Vue-Konva into my Nuxtjs project to create a drawing feature where users can freely draw rectangles on the canvas by clicking the Add Node button. However, I encountered an error: client.js:227 TypeError: Konva.Layer is not a constr ...

Crafting a recursive Typescript Immutable.js Record through typing

I am currently working on representing a tree-like data structure using immutable js and typescript. At the moment, I am utilizing regular vanilla js objects to depict the nodes within the tree. Below is the type signature. type NodeType = { value: str ...

Using the Fetch API to set variables in Vue 2: Asynchronous Task

As someone who is new to working with async tasks, I'm having trouble understanding why my fetch API call isn't updating my Vue variable even though the data appears correctly in the console log. I've tried using Async/Await without success. ...

Leverage the power of Material UI makeStyles in conjunction with Typescript

In an effort to keep things organized, I've created a specific file for the classes prop, such as MuiAlert. Is there a way to specify to makeStyles that only Alert classes should be used? The current method works, but I'm sure there's a mo ...

Is it possible to showcase a variety of values in mat-select?

Is it possible to pass different values to the .ts file in each function? For example, can I emit a String with (selectionChange)="onChangeLogr($event)" and an object with (onSelectionChange)="onChangeLogr_($event)"? How would I go about doing this? ...

Delaying the activation of the code until the image upload is complete

I'm having trouble synchronizing code to upload an image using a vue composable, wait for the upload to finish, and then store the Firebase storage URL into a database. Despite getting the URL, the success code fires before the upload is complete. My ...

Controlling the back DIV while maintaining overlapping layers

I successfully positioned my right hand content on top of the leaflet map in the background using CSS properties like position and z-index. However, I am looking for a way to make the overlapping div not only transparent but also non-interactive while stil ...

You cannot assign type 'Node | null' to type 'Node' when attempting to loop through HTML elements in TypeScript

In my code, I am taking a raw Markdown string stored in 'markdownString' and using the marked.js library to convert it to HTML for display on a web browser. My goal is to extract all plain text values from the page and store them in an array of s ...

Combining Vue components seamlessly with external HTML elements: A guide

Imagine working on a WordPress blog or a standard CMS that stores content using a wysiwyg editor like CKEditor. You might want to incorporate Vue components into your HTML, so you decide to add a wrapper div to your theme. Your HTML pages are enclosed by ...

Retrieving all rows from a table using Laravel API and Vue.js

<template> <div class="container"> <div class="row mt-5 mb-3"> <div class="col-md-10"> <h3>Gallery</h3> </div> <div class="col-md-2"> <button class="btn btn-success" ...

Incorporating SVG graphics within a SharePoint web part

I am in the process of developing a webpart for SharePoint using the SharePoint Framework, TypeScript, and ReactJS. I have encountered an issue while trying to incorporate an svg image into my webpart code, resulting in build errors. Initially, I used the ...

`vuejs does not automatically scroll to the top of a div when a form error occurs`

Having 2 components with forms on the same page using Vue.js version 2.6. The second form emits a property when its submit button is clicked, which is then received and acted upon by the first form. The form also makes an ajax request for validation purpo ...

Master the art of fetching response data from an API and implementing a function to process the data and generate desired outputs using Node.js and JavaScript

Being new to node.js, javascript, and vue, I attempted to create a Currency Converter by fetching data from an API for exchange rates and performing calculations in a function. Despite successfully obtaining the exchange rates from the selected country in ...

What is the best way to showcase images at random in Angular?

I am trying to display a random array of images in the UI, but I'm encountering an error with innerHTML when using the code below in TypeScript. randomPic(){ this.randomNum= Math.floor(Math.random() * this.myPix.length); console.log(this.rando ...

Deactivating Bootstrap Modal in Angular

Looking for advice on managing a Bootstrap Modal in Angular 7 I have a Form inside a Bootstrap Modal that I need to reset when the modal is closed (by clicking outside of it). Despite searching on Google, I haven't been able to find a solution. Any ...

What's the most efficient way to define the type of an object in TypeScript when one of its properties shares the same name as the type itself?

I'm currently working on processing an event payload where the event field is a string, and the content of data depends on the value of the event field. While I have come up with a representation that functions correctly, I can't help but feel th ...

Can you please provide guidance on how to dynamically add number values from an array to a select tag in Vue.js?

I'm trying to populate a select tag with options from an array variable that contains arrays of number values. However, the values being output appear to be blank. Here is the HTML code: <select required id="dropDown"> <option>Sele ...

Determine whether an element is visible following a state update using React Testing Library in a Next.js application

I'm looking to test a NextJS component of mine, specifically a searchbar that includes a div which should only display if the "search" state is not empty. const SearchBar = () => { const [search, setSearch] = useState(""); const handleSear ...