Frontend Development 9 min read

Dynamic Ring Chart Component with Vue 3 and Canvas

This article explains how to build a reusable Vue 3 component that draws customizable ring charts on a canvas, using TypeScript, props for size, stroke width, and a ratio list to render multiple colored arcs dynamically.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Dynamic Ring Chart Component with Vue 3 and Canvas

The author received a new requirement to render multiple tables on a page, each containing several ring charts, and was initially inclined to use ECharts, but the boss insisted on a pure CSS solution. After researching CSS ring techniques and finding them unsuitable, the author switched to a Canvas‑based implementation.

Component Overview

A reusable RingChart component is created, supporting custom ring size, stroke width, and a list of ratio‑color pairs ( ratioList ) to define each segment.

Technical Solution

The component is built with Vue 3 and TypeScript. It defines props size , storkWidth , and ratioList , provides default values, and computes the canvas center and radius.

<template>
  <canvas ref="canvasDom"></canvas>
</template>

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

interface RatioItem { ratio: number; color: string; }

const props = defineProps<{ size?: number; storkWidth?: number; ratioList?: RatioItem[] }>();

const defaultSize = 200;
const defaultStorkWidth = 4;
const defaultRatioList: RatioItem[] = [{ ratio: 1, color: '#C4C9CF4D' }];

const canvasDom = ref<HTMLCanvasElement | null>(null);
let ctx: CanvasRenderingContext2D | null = null;

const size = computed(() => props.size || defaultSize);
const center = computed(() => ({ x: size.value / 2, y: size.value / 2 }));
const radius = computed(() => size.value / 2 - (props.storkWidth || defaultStorkWidth));

const initCanvas = () => {
  const dom = canvasDom.value;
  if (!dom) return;
  ctx = dom.getContext('2d');
  if (!ctx) return;
  dom.width = size.value;
  dom.height = size.value;
  drawBackgroundCircle();
  drawDataRings();
};

const drawBackgroundCircle = () => {
  if (!ctx) return;
  drawCircle({ ctx, x: center.value.x, y: center.value.y, radius: radius.value,
    lineWidth: props.storkWidth || defaultStorkWidth, color: '#C4C9CF4D',
    startAngle: -Math.PI / 2, endAngle: Math.PI * 1.5 });
};

const drawDataRings = () => {
  const { ratioList = defaultRatioList } = props;
  if (!ctx) return;
  let startAngle = -Math.PI / 2;
  ratioList.forEach(({ ratio, color }) => {
    const endAngle = startAngle + ratio * Math.PI * 2;
    drawCircle({ ctx, x: center.value.x, y: center.value.y, radius: radius.value,
      lineWidth: props.storkWidth || defaultStorkWidth, color, startAngle, endAngle });
    startAngle = endAngle;
  });
};

const drawCircle = ({ ctx, x, y, radius, lineWidth, color, startAngle, endAngle }: {
  ctx: CanvasRenderingContext2D; x: number; y: number; radius: number;
  lineWidth: number; color: string; startAngle: number; endAngle: number;
}) => {
  ctx.beginPath();
  ctx.arc(x, y, radius, startAngle, endAngle);
  ctx.lineWidth = lineWidth;
  ctx.strokeStyle = color;
  ctx.stroke();
  ctx.closePath();
};

watchEffect(() => { initCanvas(); });
onMounted(() => { initCanvas(); });
</script>

<style scoped>
canvas { display: block; margin: auto; border-radius: 50%; }
</style>

The component initializes the canvas, draws a background ring, and then iterates over ratioList to render each data ring using the arc method, adjusting lineWidth and strokeStyle for visual styling.

Finally, the component can be used elsewhere with a simple tag:

<RingChart :storkWidth="8" :size="60" :ratioList="[
  { ratio: 0.3, color: '#F8766F' },
  { ratio: 0.6, color: '#69CD90' },
  { ratio: 0.1, color: '#FFB800' }
]"></RingChart>
frontendtypescriptcanvascomponentVueData VisualizationRing Chart
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.