Home > Designing, Others > Building a Donut Chart with Vue and SVG

Building a Donut Chart with Vue and SVG

November 7th, 2018 Leave a comment Go to comments

Mmm… forbidden donut.”

– Homer Simpson

I recently needed to make a donut chart for a reporting dashboard at work. The mock-up that I got looked something like this:

My chart had a few basic requirements. It needed to:

  • Dynamically calculate its segments based on an arbitrary set of values
  • Have labels
  • Scale well across all screen sizes and devices
  • Be cross-browser compatible back to Internet Explorer 11
  • Be accessible
  • Be reusable across my work’s Vue.js front end

I also wanted something that I could animate later if I needed to. All of this sounded like a job for SVG.

SVGs are accessible out-of-the-box (the W3C has a whole section on this) and can be made more accessible through additional input. And, because they’re powered by data, they’re a perfect candidate for dynamic visualization.

There are plenty of articles on the topic, including two by Chris (here and here) and a super recent one by Burke Holland. I didn’t use D3 for this project because the application didn’t need the overhead of that library.

I created the chart as a Vue component for my project, but you could just as easily do this with vanilla JavaScript, HTML, and CSS.

Here’s the finished product:

See the Pen Vue Donut Chart – Final Version by Salomone Baquis (@soluhmin) on CodePen.

Reinventing the wheel circle

Like any self-respecting developer, the first thing I did was Google to see if someone else had already made this. Then, like same said developer, I scrapped the pre-built solution in favor of my own.

The top hit for “SVG donut chart” is this article, which describes how to use stroke-dasharray and stroke-dashoffset to draw multiple overlaid circles and create the illusion of a single segmented circle (more on this shortly).

I really like the overlay concept, but found recalculating both stroke-dasharray and stroke-dashoffset values confusing. Why not set one fixed stroke-dasharrary value and then rotate each circle with a transform? I also needed to add labels to each segment, which wasn’t covered in the tutorial.

Drawing a line

Before we can create a dynamic donut chart, we first need to understand how SVG line drawing works. If you haven’t read Jake Archibald’s excellent Animated Line Drawing in SVG. Chris also has a good overview.

Those articles provide most of the context you’ll need, but briefly, SVG has two presentation attributes: stroke-dasharray and stroke-dashoffset.

stroke-dasharray defines an array of dashes and gaps used to paint the outline of a shape. It can take zero, one, or two values. The first value defines the dash length; the second defines the gap length.

stroke-dashoffset, on the other hand, defines where the set of dashes and gaps begins. If the stroke-dasharray and the stroke-dashoffset values are the length of the line and equal, the entire line is visible because we’re telling the offset (where the dash-array starts) to begin at the end of the line. If the stroke-dasharray is the length of the line, but the stroke-dashoffset is 0, then the line is invisible because we’re offsetting the rendered part of the dash by its entire length.

Chris’ example demonstrates this nicely:

See the Pen Basic Example of SVG Line Drawing, Backward and Forward by Chris Coyier (@chriscoyier) on CodePen.

How we’ll build the chart

To create the donut chart’s segments, we’ll make a separate circle for each one, overlay the circles on top of one another, then use stroke, stroke-dasharray, and stroke-dashoffset to show only part of the stroke of each circle. We’ll then rotate each visible part into the correct position, creating the illusion of a single shape. As we do this, we’ll also calculate the coordinates for the text labels.

Here’s an example demonstrating these rotations and overlays:

See the Pen Circle Overlays by Salomone Baquis (@soluhmin) on CodePen.

Basic setup

Let’s start by setting up our structure. I’m using x-template for demo purposes, but I’d recommend creating a single file component for production.

<div id="app">
  <donut-chart></donut-chart>
</div>
<script type="text/x-template" id="donutTemplate">
  <svg height="160" width="160" viewBox="0 0 160 160">
    <g v-for="(value, index) in initialValues">
      <circle :cx="cx" :cy="cy" :r="radius" fill="transparent" :stroke="colors[index]" :stroke-width="strokeWidth" ></circle>
      <text></text>
    </g>
  </svg>
</script>
Vue.component('donutChart', {
  template: '#donutTemplate',
  props: ["initialValues"],
  data() {
    return {
      chartData: [],
      colors: ["#6495ED", "goldenrod", "#cd5c5c", "thistle", "lightgray"],
      cx: 80,
      cy: 80,                      
      radius: 60,
      sortedValues: [],
      strokeWidth: 30,    
    }
  }  
})
new Vue({
  el: "#app",
  data() {
    return {
      values: [230, 308, 520, 130, 200]
    }
  },
});

With this, we:

  • Create our Vue instance and our donut chart component, then tell our donut component to expect some values (our dataset) as props
  • Establish our basic SVG shapes: for the segments and for the labels, with the basic dimensions, stroke width, and colors defined
  • Wrap these shapes in a element, which groups them together
  • Add a v-for loop to the g> element, which we’ll use to iterate through each value that the component receives
  • Create an empty sortedValues array, which we’ll use to hold a sorted version of our data
  • Create an empty chartData array, which will contain our main positioning data

Circle length

Our stroke-dasharray should be the length of the entire circle, giving us an easy baseline number which we can use to calculate each stroke-dashoffset value. Recall that the length of a circle is its circumference and the formula for circumference is 2?r (you remember this, right?).

We can make this a computed property in our component.

computed: {
  circumference() {
    return 2 * Math.PI * this.radius
  }
}

…and bind the value to our template markup.

<svg height="160" width="160" viewBox="0 0 160 160">
  <g v-for="(value, index) in initialValues">
    <circle :cx="cx" :cy="cy" :r="radius" fill="transparent" :stroke="colors[index]" :stroke-width="strokeWidth" :stroke-dasharray="circumference" ></circle>
    <text></text>
  </g>
</svg>

In the initial mockup, we saw that the segments went from largest to smallest. We can make another computed property to sort these. We’ll store the sorted version inside the sortedValues array.

sortInitialValues() {
  return this.sortedValues = this.initialValues.sort((a,b) => b-a)
}

Finally, in order for these sorted values to be available to Vue before the chart gets rendered, we’ll want to reference this computed property from the mounted() lifecycle hook.

mounted() {
  this.sortInitialValues                
}

Right now, our chart look like this:

See the Pen Donut Chart – No Segments by Salomone Baquis (@soluhmin) on CodePen.

No segments. Just a solid-colored donut. Like HTML, SVG elements are rendered in the order that they appear in the markup. The color that appears is the stroke color of the last circle in the SVG. Because we haven’t added any stroke-dashoffset values yet, each circle’s stroke goes all the way around. Let’s fix this by creating segments.

Creating segments

To get each of the circle segments, we’ll need to:

  1. Calculate the percentage of each data value from the total data values that we pass in
  2. Multiply this percentage by the circumference to get the length of the visible stroke
  3. Subtract this length from the circumference to get the stroke-offset

It sounds more complicated than it is. Let’s start with some helper functions. We first need to total up our data values. We can use a computed property to do this.

dataTotal() {
  return this.sortedValues.reduce((acc, val) => acc + val)
},

To calculate the percentage of each data value, we’ll need to pass in values from the v-for loop that we created earlier, which means that we’ll need to add a method.

methods: {
  dataPercentage(dataVal) {
    return dataVal / this.dataTotal
  }
},

We now have enough information to calculate our stroke-offset values, which will establish our circle segments.

Again, we want to: (a) multiply our data percentage by the circle circumference to get the length of the visible stroke, and (b) subtract this length from the circumference to get the stroke-offset.

Here’s the method to get our stroke-offsets:

calculateStrokeDashOffset(dataVal, circumference) {
  const strokeDiff = this.dataPercentage(dataVal) * circumference
  return circumference - strokeDiff
},

…which we bind to our circle in the HTML with:

:stroke-dashoffset="calculateStrokeDashOffset(value, circumference)"

And voilà! We should have something like this:

See the Pen Donut Chart – No Rotations by Salomone Baquis (@soluhmin) on CodePen.

Rotating segments

Now the fun part. All of segments begin at 3 o’clock, which is the default starting point for SVG circles. To get them in the right place, we need to rotate each segment to its correct position.

We can do this by finding each segment’s ratio out of 360 degrees and then offset that amount by the total degrees that came before it.

First, let’s add a data property to keep track of the offset:

angleOffset: -90,

Then our calculation (this is a computed property):

calculateChartData() {
  this.sortedValues.forEach((dataVal, index) => {
    const data = {
      degrees: this.angleOffset,
    }
    this.chartData.push(data)
    this.angleOffset = this.dataPercentage(dataVal) * 360 + this.angleOffset
  })
},

Each loop creates a new object with a “degrees” property, pushes that into our chartValues array that we created earlier, and then updates the angleOffset for the next loop.

But wait, what’s up with the -90 value?

Well, looking back at our original mockup, the first segment is shown at the 12 o’clock position, or -90 degrees from the starting point. By setting our angleOffset at -90, we ensure that our largest donut segment starts from the top.

To rotate these segments in the HTML, we’ll use the transform presentation attribute with the rotate function. Let’s create another computed property so that we can return a nice, formatted string.

returnCircleTransformValue(index) {
  return rotate(${this.chartData[index].degrees}, ${this.cx}, ${this.cy})`
},

The rotate function takes three arguments: an angle of rotation and x and y coordinates around which the angle rotates. If we don’t supply cx and cy coordinates, then our segments will rotate around the entire SVG coordinate system.

Next, we bind this to our circle markup.

:transform="returnCircleTransformValue(index)"

And, since we need to do all of these calculations before the chart is rendered, we’ll add our calculateChartData computed property in the mounted hook:

mounted() {
  this.sortInitialValues
  this.calculateChartData
}

Finally, if we want that sweet, sweet gap between each segment, we can subtract two from the circumference and use this as our new stroke-dasharray.

adjustedCircumference() {
  return this.circumference - 2
},
:stroke-dasharray="adjustedCircumference"

Segments, baby!

See the Pen Donut Chart – Segments Only by Salomone Baquis (@soluhmin) on CodePen.

Labels

We have our segments, but now we need to create labels. This means that we need to place our elements with x and y coordinates at different points along the circle. You might suspect that this requires math. Sadly, you are correct.

Fortunately, this isn’t the kind of math where we need to apply Real Concepts; this is more the kind where we Google formulas and don’t ask too many questions.

According to the Internet, the formulas to calculate x and y points along a circle are:

x = r cos(t) + a
y = r sin(t) + b

…where r is the radius, t is the angle, and a and b are the x and y center point offsets.

We already have most of this: we know our radius, we know how to calculate our segment angles, and we know our center offset values (cx and cy).

There’s one catch, though: in those formulas, t is in *radians*. We’re working in degrees, which means that we need to do some conversions. Again, a quick search turns up a formula:

radians = degrees * (π / 180)

…which we can represent in a method:

degreesToRadians(angle) {
  return angle * (Math.PI / 180)
},

We now have enough information to calculate our x and y text coordinates:

calculateTextCoords(dataVal, angleOffset) {
  const angle = (this.dataPercentage(dataVal) * 360) / 2 + angleOffset
  const radians = this.degreesToRadians(angle)

  const textCoords = {
    x: (this.radius * Math.cos(radians) + this.cx),
    y: (this.radius * Math.sin(radians) + this.cy)
  }
  return textCoords
},

First, we calculate the angle of our segment by multiplying the ratio of our data value by 360; however, we actually want half of this because our text labels are in the middle of the segment rather than the end. We need to add the angle offset like we did when we created the segments.

Our calculateTextCoords method can now be used in the calculateChartData computed property:

calculateChartData() {
  this.sortedValues.forEach((dataVal, index) => {
    const { x, y } = this.calculateTextCoords(dataVal, this.angleOffset)        
    const data = {
      degrees: this.angleOffset,
      textX: x,
      textY: y
    }
    this.chartData.push(data)
    this.angleOffset = this.dataPercentage(dataVal) * 360 + this.angleOffset
  })
},

Let’s also add a method to return the label string:

percentageLabel(dataVal) {
  return `${Math.round(this.dataPercentage(dataVal) * 100)}%`
},

And, in the markup:

<text :x="chartData[index].textX" :y="chartData[index].textY">{{ percentageLabel(value) }}</text>

Now we’ve got labels:

See the Pen Donut Chart – Unformatted Labels by Salomone Baquis (@soluhmin) on CodePen.

Blech, so off-center. We can fix this with the text-anchor presentation attribute. Depending on your font and font-size, you may want to adjust the positioning as well. Check out dx and dy for this.

Revamped text element:

<text text-anchor="middle" dy="3px" :x="chartData[index].textX" :y="chartData[index].textY">{{ percentageLabel(value) }}</text>

Hmm, it seems that if we have small percentages, the labels go outside of the segments. Let’s add a method to check for this.

segmentBigEnough(dataVal) {
  return Math.round(this.dataPercentage(dataVal) * 100) > 5
}
<text v-if="segmentBigEnough(value)" text-anchor="middle" dy="3px" :x="chartData[index].textX" :y="chartData[index].textY">{{ percentageLabel(value) }}</text>

Now, we’ll only add labels to segments larger than 5%.

And we’re done! We now have a reusable donut chart component that can accept any set of values and create segments. Super cool!

The finished product:

See the Pen Vue Donut Chart – Final Version by Salomone Baquis (@soluhmin) on CodePen.

Next steps

There are lots of ways that we can modify or improve this now that it’s built. For example:

  • Adding elements to enhance accessibility, such as </code> and <code></code> tags, aria-labels, and aria role attributes.</li> <li>Creating <strong>animations</strong> with CSS or libraries like <a target="_blank" href="https://greensock.com/">Greensock</a> to create eye-catching effects when the chart comes into view.</li> <li>Playing with <strong>color schemes</strong>.</li> </ul> <p>I’d love to hear what you think about this implementation and other experiences you’ve had with SVG charts. Share in the comments!</p> <p>The post <a target="_blank" rel="nofollow" href="https://css-tricks.com/building-a-donut-chart-with-vue-and-svg/">Building a Donut Chart with Vue and SVG</a> appeared first on <a target="_blank" rel="nofollow" href="https://css-tricks.com/">CSS-Tricks</a>.</p> <!-- Start Sociable --><div class="sociable"><div class="sociable_tagline">Be Sociable, Share!</div><ul class='clearfix'><li><a title="Twitter" class="option1_32" style="background-position:-288px -32px" rel="nofollow" target="_blank" href="http://twitter.com/intent/tweet?text=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG%20-%20http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F%20 "></a></li><li><a title="Facebook" class="option1_32" style="background-position:-96px 0px" rel="nofollow" target="_blank" href="http://www.facebook.com/share.php?u=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&t=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG"></a></li><li><a title="email" class="option1_32" style="background-position:-160px 0px" rel="nofollow" target="_blank" href="https://mail.google.com/mail/?view=cm&fs=1&to&su=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG&body=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&ui=2&tf=1&shva=1"></a></li><li><a onClick="javascript:var ipinsite='Good%20Vibes.%20Vuible.com',ipinsiteurl='http://vuible.com/';(function(){if(window.ipinit!==undefined){ipinit();}else{document.body.appendChild(document.createElement('script')).src='http://vuible.com/wp-content/themes/ipinpro/js/ipinit.js';}})();" style="cursor:pointer" rel="nofollow" title="Vuible.com | Share positive messages (images and videos only)"><img style='' src='http://www.webmastersgallery.com/wp-content/plugins/sociable/images/option1/32/vuible.png'></a></li><li><a class="option1_32" style="cursor:pointer;background-position:-128px 0px" rel="nofollow" title="Add to favorites - doesn't work in Chrome" onClick="javascript:AddToFavorites();"></a></li><li><a title="StumbleUpon" class="option1_32" style="background-position:-224px -32px" rel="nofollow" target="_blank" href="http://www.stumbleupon.com/submit?url=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&title=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG"></a></li><li><a title="Delicious" class="option1_32" style="background-position:-32px 0px" rel="nofollow" target="_blank" href="http://delicious.com/post?url=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&title=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG&notes=%0AMmm...%20forbidden%20donut.%22%0A-%20Homer%20Simpson%0A%0AI%20recently%20needed%20to%20make%20a%20donut%20chart%20for%20a%20reporting%20dashboard%20at%20work.%20The%20mock-up%20that%20I%20got%20looked%20something%20like%20this%3A%20%0A%0AMy%20chart%20had%20a%20few%20basic%20requirements.%20It%20needed%20to%3A%0ADynamically%20calculate%20its%20"></a></li><li><a title="Google Reader" class="option1_32" style="background-position:-224px 0px" rel="nofollow" target="_blank" href="http://www.google.com/reader/link?url=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&title=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG&srcURL=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&srcTitle=Webmasters+Gallery+"></a></li><li><a title="LinkedIn" class="option1_32" style="background-position:-288px 0px" rel="nofollow" target="_blank" href="http://www.linkedin.com/shareArticle?mini=true&url=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&title=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG&source=Webmasters+Gallery+&summary=%0AMmm...%20forbidden%20donut.%22%0A-%20Homer%20Simpson%0A%0AI%20recently%20needed%20to%20make%20a%20donut%20chart%20for%20a%20reporting%20dashboard%20at%20work.%20The%20mock-up%20that%20I%20got%20looked%20something%20like%20this%3A%20%0A%0AMy%20chart%20had%20a%20few%20basic%20requirements.%20It%20needed%20to%3A%0ADynamically%20calculate%20its%20"></a></li><li><a style="cursor:pointer" rel="nofollow" onMouseOut="fixOnMouseOut(document.getElementById('sociable-post-1809282'), event, 'post-1809282')" onMouseOver="more(this,'post-1809282')"><img style='margin-top:9px' src='http://www.webmastersgallery.com/wp-content/plugins/sociable/images/more.png'></a></li></ul><div onMouseout="fixOnMouseOut(this,event,'post-1809282')" id="sociable-post-1809282" style="display:none;"> <div style="top: auto; left: auto; display: block;" id="sociable"> <div class="popup"> <div class="content"> <ul><li style="heigth:32px;width:32px"><a title="Myspace" class="option1_32" style="background-position:0px -32px" rel="nofollow" target="_blank" href="http://www.myspace.com/Modules/PostTo/Pages/?u=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&t=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG"></a></li><li style="heigth:32px;width:32px"><a title="Digg" class="option1_32" style="background-position:-64px 0px" rel="nofollow" target="_blank" href="http://digg.com/submit?phase=2&url=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&title=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG&bodytext=%0AMmm...%20forbidden%20donut.%22%0A-%20Homer%20Simpson%0A%0AI%20recently%20needed%20to%20make%20a%20donut%20chart%20for%20a%20reporting%20dashboard%20at%20work.%20The%20mock-up%20that%20I%20got%20looked%20something%20like%20this%3A%20%0A%0AMy%20chart%20had%20a%20few%20basic%20requirements.%20It%20needed%20to%3A%0ADynamically%20calculate%20its%20"></a></li><li style="heigth:32px;width:32px"><a title="Reddit" class="option1_32" style="background-position:-128px -32px" rel="nofollow" target="_blank" href="http://reddit.com/submit?url=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&title=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG"></a></li><li style="heigth:32px;width:32px"><a title="Google Bookmarks" class="option1_32" style="background-position:-192px 0px" rel="nofollow" target="_blank" href="http://www.google.com/bookmarks/mark?op=edit&bkmk=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&title=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG&annotation=%0AMmm...%20forbidden%20donut.%22%0A-%20Homer%20Simpson%0A%0AI%20recently%20needed%20to%20make%20a%20donut%20chart%20for%20a%20reporting%20dashboard%20at%20work.%20The%20mock-up%20that%20I%20got%20looked%20something%20like%20this%3A%20%0A%0AMy%20chart%20had%20a%20few%20basic%20requirements.%20It%20needed%20to%3A%0ADynamically%20calculate%20its%20"></a></li><li style="heigth:32px;width:32px"><a title="HackerNews" class="option1_32" style="background-position:-256px 0px" rel="nofollow" target="_blank" href="http://news.ycombinator.com/submitlink?u=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&t=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG"></a></li><li style="heigth:32px;width:32px"><a title="MSNReporter" class="option1_32" style="background-position:-352px 0px" rel="nofollow" target="_blank" href="http://reporter.es.msn.com/?fn=contribute&Title=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG&URL=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&cat_id=6&tag_id=31&Remark=%0AMmm...%20forbidden%20donut.%22%0A-%20Homer%20Simpson%0A%0AI%20recently%20needed%20to%20make%20a%20donut%20chart%20for%20a%20reporting%20dashboard%20at%20work.%20The%20mock-up%20that%20I%20got%20looked%20something%20like%20this%3A%20%0A%0AMy%20chart%20had%20a%20few%20basic%20requirements.%20It%20needed%20to%3A%0ADynamically%20calculate%20its%20"></a></li><li style="heigth:32px;width:32px"><a title="BlinkList" class="option1_32" style="background-position:0px 0px" rel="nofollow" target="_blank" href="http://www.blinklist.com/index.php?Action=Blink/addblink.php&Url=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&Title=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG"></a></li><li style="heigth:32px;width:32px"><a title="Sphinn" class="option1_32" style="background-position:-192px -32px" rel="nofollow" target="_blank" href="http://sphinn.com/index.php?c=post&m=submit&link=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F"></a></li><li style="heigth:32px;width:32px"><a title="Posterous" class="option1_32" style="background-position:-64px -32px" rel="nofollow" target="_blank" href="http://posterous.com/share?linkto=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&title=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG&selection=%0AMmm...%20forbidden%20donut.%22%0A-%20Homer%20Simpson%0A%0AI%20recently%20needed%20to%20make%20a%20donut%20chart%20for%20a%20reporting%20dashboard%20at%20work.%20The%20mock-up%20that%20I%20got%20looked%20something%20like%20this%3A%20%0A%0AMy%20chart%20had%20a%20few%20basic%20requirements.%20It%20needed%20to%3A%0ADynamically%20calculate%20its%20"></a></li><li style="heigth:32px;width:32px"><a title="Tumblr" class="option1_32" style="background-position:-256px -32px" rel="nofollow" target="_blank" href="http://www.tumblr.com/share?v=3&u=http%3A%2F%2Fwww.webmastersgallery.com%2F2018%2F11%2F07%2Fbuilding-a-donut-chart-with-vue-and-svg%2F&t=Building%20a%20Donut%20Chart%20with%20Vue%20and%20SVG&s=%0AMmm...%20forbidden%20donut.%22%0A-%20Homer%20Simpson%0A%0AI%20recently%20needed%20to%20make%20a%20donut%20chart%20for%20a%20reporting%20dashboard%20at%20work.%20The%20mock-up%20that%20I%20got%20looked%20something%20like%20this%3A%20%0A%0AMy%20chart%20had%20a%20few%20basic%20requirements.%20It%20needed%20to%3A%0ADynamically%20calculate%20its%20"></a></li></ul> </div> <a style="cursor:pointer" onclick="hide_sociable('post-1809282',true)" class="close"> <img onclick="hide_sociable('post-1809282',true)" title="close" src="http://www.webmastersgallery.com/wp-content/plugins/sociable/images/closelabel.png"> </a> </div> </div> </div></div><div class='sociable' style='float:none'><ul class='clearfix'><li id="Twitter_Counter"><a href="https://twitter.com/share" data-text="Building a Donut Chart with Vue and SVG - http://www.webmastersgallery.com/2018/11/07/building-a-donut-chart-with-vue-and-svg/" data-url="http://www.webmastersgallery.com/2018/11/07/building-a-donut-chart-with-vue-and-svg/" class="twitter-share-button" data-count="horizontal">Tweet</a><script type="text/javascript" src="//platform.twitter.com/widgets.js"></script></li><li id="Facebook_Counter"><iframe src="http://www.facebook.com/plugins/like.php?href=http://www.webmastersgallery.com/2018/11/07/building-a-donut-chart-with-vue-and-svg/&send=false&layout=button_count&show_faces=false&action=like&colorscheme=light&font" scrolling="no" frameborder="0" style="border:none; overflow:hidden;height:32px;width:100px" allowTransparency="true"></iframe></li><li id="Google_p"><g:plusone annotation="bubble" href="http://www.webmastersgallery.com/2018/11/07/building-a-donut-chart-with-vue-and-svg/" size="medium"></g:plusone></li><li id="LinkedIn_Counter"><script src="http://platform.linkedin.com/in.js" type="text/javascript"></script><script type="IN/Share" data-url="http://www.webmastersgallery.com/2018/11/07/building-a-donut-chart-with-vue-and-svg/" data-counter="right"></script></li><li id="StumbleUpon_Counter"><script src="http://www.stumbleupon.com/hostedbadge.php?s=2&r=http://www.webmastersgallery.com/2018/11/07/building-a-donut-chart-with-vue-and-svg/"></script></li><li id="vuible_Counter"><a title='Vuible.com | Share positive messages (images and videos only)'><img onClick='ipinit();' style='cursor:pointer' src='http://www.webmastersgallery.com/wp-content/plugins/sociable/images/vuible.png'></a></li></ul></div><!-- End Sociable --> <div class="fixed"></div> </div> <div class="under"> <span class="categories">Categories: </span><span><a href="http://www.webmastersgallery.com/category/design/" rel="category tag">Designing</a>, <a href="http://www.webmastersgallery.com/category/uncategorized/" rel="category tag">Others</a></span> <span class="tags">Tags: </span><span></span> </div> </div> <!-- related posts START --> <!-- related posts END --> <script type="text/javascript" src="http://www.webmastersgallery.com/wp-content/themes/inove/js/comment.js"></script> <div id="comments"> <div id="cmtswitcher"> <a id="commenttab" class="curtab" href="javascript:void(0);" onclick="MGJS.switchTab('thecomments,commentnavi', 'thetrackbacks', 'commenttab', 'curtab', 'trackbacktab', 'tab');">Comments (0)</a> <a id="trackbacktab" class="tab" href="javascript:void(0);" onclick="MGJS.switchTab('thetrackbacks', 'thecomments,commentnavi', 'trackbacktab', 'curtab', 'commenttab', 'tab');">Trackbacks (0)</a> <span class="addcomment"><a href="#respond">Leave a comment</a></span> <span class="addtrackback"><a href="http://www.webmastersgallery.com/2018/11/07/building-a-donut-chart-with-vue-and-svg/trackback/">Trackback</a></span> <div class="fixed"></div> </div> <div id="commentlist"> <!-- comments START --> <ol id="thecomments"> <li class="messagebox"> No comments yet. </li> </ol> <!-- comments END --> <!-- trackbacks START --> <ol id="thetrackbacks"> <li class="messagebox"> No trackbacks yet. </li> </ol> <div class="fixed"></div> <!-- trackbacks END --> </div> </div> <form action="http://www.webmastersgallery.com/wp-comments-post.php" method="post" id="commentform"> <div id="respond"> <div id="author_info"> <div class="row"> <input type="text" name="author" id="author" class="textfield" value="" size="24" tabindex="1" /> <label for="author" class="small">Name (required)</label> </div> <div class="row"> <input type="text" name="email" id="email" class="textfield" value="" size="24" tabindex="2" /> <label for="email" class="small">E-Mail (will not be published) (required)</label> </div> <div class="row"> <input type="text" name="url" id="url" class="textfield" value="" size="24" tabindex="3" /> <label for="url" class="small">Website</label> </div> </div> <!-- comment input --> <div class="row"> <textarea name="comment" id="comment" tabindex="4" rows="8" cols="50"></textarea> </div> <!-- comment submit and rss --> <div id="submitbox"> <a class="feed" href="http://www.webmastersgallery.com/comments/feed/">Subscribe to comments feed</a> <div class="submitbutton"> <input name="submit" type="submit" id="submit" class="button" tabindex="5" value="Submit Comment" /> </div> <input type="hidden" name="comment_post_ID" value="1809282" /> <div class="fixed"></div> </div> </div> <p style="display: none;"><input type="hidden" id="akismet_comment_nonce" name="akismet_comment_nonce" value="c861b16593" /></p><p style="display: none;"><input type="hidden" id="ak_js" name="ak_js" value="210"/></p> </form> <div id="postnavi"> <span class="prev"><a href="http://www.webmastersgallery.com/2018/11/07/simplify-styling-with-functional-css/" rel="next">Simplify Styling with Functional CSS</a></span> <span class="next"><a href="http://www.webmastersgallery.com/2018/11/07/how-to-design-a-payment-form-your-customers-will-actually-complete/" rel="prev">How to design a payment form your customers will actually complete</a></span> <div class="fixed"></div> </div> </div> <!-- main END --> <!-- sidebar START --> <div id="sidebar"> <!-- sidebar north START --> <div id="northsidebar" class="sidebar"> <!-- feeds --> <div class="widget widget_feeds"> <div class="content"> <div id="subscribe"> <a rel="external nofollow" id="feedrss" title="Subscribe to this blog..." href="http://www.webmastersgallery.com/feed/"><abbr title="Really Simple Syndication">RSS</abbr></a> </div> <div class="fixed"></div> </div> </div> <!-- showcase --> <div id="text-389627311" class="widget widget_text"> <div class="textwidget"><a href="http://feeds2.feedburner.com/webmastersgallery" title="Subscribe to my feed" rel="alternate" type="application/rss+xml"><img src="http://www.feedburner.com/fb/images/pub/feed-icon32x32.png" alt="" style="border:0"/></a><a href="http://feeds2.feedburner.com/webmastersgallery" title="Subscribe to my feed" rel="alternate" type="application/rss+xml">Subscribe for latest Updates</a></div> </div><div id="text-389629461" class="widget widget_text"> <div class="textwidget"><form style="border:1px solid #ccc;padding:3px;text-align:center;" action="http://feedburner.google.com/fb/a/mailverify" method="post" target="popupwindow" onsubmit="window.open('http://feedburner.google.com/fb/a/mailverify?uri=webmastersgallery', 'popupwindow', 'scrollbars=yes,width=550,height=520');return true"><p>Enter your email address:</p><p><input type="text" style="width:140px" name="email"/></p><input type="hidden" value="webmastersgallery" name="uri"/><input type="hidden" name="loc" value="en_US"/><input type="submit" value="Subscribe" /><p>Delivered by <a href="http://feedburner.google.com" target="_blank">FeedBurner</a></p></form></div> </div></div> <!-- sidebar north END --> <div id="centersidebar"> <!-- sidebar east START --> <div id="eastsidebar" class="sidebar"> <!-- categories --> <div class="widget widget_categories"> <h3>Categories</h3> <ul> <li class="cat-item cat-item-518"><a href="http://www.webmastersgallery.com/category/affiliate-programs/" >Affiliate Programs</a> </li> <li class="cat-item cat-item-147"><a href="http://www.webmastersgallery.com/category/design/" >Designing</a> </li> <li class="cat-item cat-item-519"><a href="http://www.webmastersgallery.com/category/domain-names/" >Domain Names</a> </li> <li class="cat-item cat-item-37"><a href="http://www.webmastersgallery.com/category/e-commerce/" >E-commerce</a> </li> <li class="cat-item cat-item-509"><a href="http://www.webmastersgallery.com/category/internet-directories/" >Internet Directories</a> </li> <li class="cat-item cat-item-510"><a href="http://www.webmastersgallery.com/category/message-boards/" >Message Boards</a> </li> <li class="cat-item cat-item-1"><a href="http://www.webmastersgallery.com/category/uncategorized/" >Others</a> </li> <li class="cat-item cat-item-506"><a href="http://www.webmastersgallery.com/category/programming/" >Programming</a> </li> <li class="cat-item cat-item-511"><a href="http://www.webmastersgallery.com/category/promotion-and-marketing/" >Promotion and Marketing</a> </li> <li class="cat-item cat-item-534"><a href="http://www.webmastersgallery.com/category/scripts-and-programming/" >Scripts and Programming</a> </li> <li class="cat-item cat-item-513"><a href="http://www.webmastersgallery.com/category/search-engines/" >Search Engines</a> </li> <li class="cat-item cat-item-135"><a href="http://www.webmastersgallery.com/category/social-media/" >Social Media</a> </li> <li class="cat-item cat-item-514"><a href="http://www.webmastersgallery.com/category/softwares/" >Softwares</a> </li> <li class="cat-item cat-item-515"><a href="http://www.webmastersgallery.com/category/tips-and-tutorials/" >Tips and Tutorials</a> </li> <li class="cat-item cat-item-338"><a href="http://www.webmastersgallery.com/category/web-hosting/" >Web Hosting</a> </li> <li class="cat-item cat-item-516"><a href="http://www.webmastersgallery.com/category/webmaster-tools/" >Webmaster Tools</a> </li> <li class="cat-item cat-item-501"><a href="http://www.webmastersgallery.com/category/webmaster-resources/" title="Webmaster Resources Free Web Tools description: The ultimate webmaster resource directory. We have all the webmaster resources and free web tools a webmaster could ever need to design, develop.">Webmasters Resources</a> </li> <li class="cat-item cat-item-3"><a href="http://www.webmastersgallery.com/category/web-design/" >Website Design</a> </li> </ul> </div> </div> <!-- sidebar east END --> <!-- sidebar west START --> <div id="westsidebar" class="sidebar"> <!-- blogroll --> <div class="widget widget_links"> <h3>Blogroll</h3> <ul> <li><a href="http://wordpress.org/development/">Development Blog</a></li> <li><a href="http://codex.wordpress.org/">Documentation</a></li> <li><a href="http://wordpress.org/extend/plugins/">Plugins</a></li> <li><a href="http://wordpress.org/extend/ideas/">Suggest Ideas</a></li> <li><a href="http://wordpress.org/support/">Support Forum</a></li> <li><a href="http://wordpress.org/extend/themes/">Themes</a></li> <li><a href="http://planet.wordpress.org/">WordPress Planet</a></li> </ul> </div> </div> <!-- sidebar west END --> <div class="fixed"></div> </div> <!-- sidebar south START --> <div id="southsidebar" class="sidebar"> <!-- archives --> <div class="widget"> <h3>Archives</h3> <ul> <li><a href='http://www.webmastersgallery.com/2018/11/'>November 2018</a></li> <li><a href='http://www.webmastersgallery.com/2018/10/'>October 2018</a></li> <li><a href='http://www.webmastersgallery.com/2018/09/'>September 2018</a></li> <li><a href='http://www.webmastersgallery.com/2018/08/'>August 2018</a></li> <li><a href='http://www.webmastersgallery.com/2018/07/'>July 2018</a></li> <li><a href='http://www.webmastersgallery.com/2018/04/'>April 2018</a></li> <li><a href='http://www.webmastersgallery.com/2018/01/'>January 2018</a></li> <li><a href='http://www.webmastersgallery.com/2017/12/'>December 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/11/'>November 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/09/'>September 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/08/'>August 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/07/'>July 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/06/'>June 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/05/'>May 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/04/'>April 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/03/'>March 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/02/'>February 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/01/'>January 2017</a></li> <li><a href='http://www.webmastersgallery.com/2016/12/'>December 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/11/'>November 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/10/'>October 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/09/'>September 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/08/'>August 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/07/'>July 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/06/'>June 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/05/'>May 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/04/'>April 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/03/'>March 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/02/'>February 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/01/'>January 2016</a></li> <li><a href='http://www.webmastersgallery.com/2015/12/'>December 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/11/'>November 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/10/'>October 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/09/'>September 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/08/'>August 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/07/'>July 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/06/'>June 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/05/'>May 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/04/'>April 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/03/'>March 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/02/'>February 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/01/'>January 2015</a></li> <li><a href='http://www.webmastersgallery.com/2014/12/'>December 2014</a></li> <li><a href='http://www.webmastersgallery.com/2014/11/'>November 2014</a></li> <li><a href='http://www.webmastersgallery.com/2014/10/'>October 2014</a></li> <li><a href='http://www.webmastersgallery.com/2014/09/'>September 2014</a></li> <li><a href='http://www.webmastersgallery.com/2014/08/'>August 2014</a></li> <li><a href='http://www.webmastersgallery.com/2014/07/'>July 2014</a></li> <li><a href='http://www.webmastersgallery.com/2014/06/'>June 2014</a></li> <li><a href='http://www.webmastersgallery.com/2013/07/'>July 2013</a></li> <li><a href='http://www.webmastersgallery.com/2013/01/'>January 2013</a></li> <li><a href='http://www.webmastersgallery.com/2012/12/'>December 2012</a></li> <li><a href='http://www.webmastersgallery.com/2012/08/'>August 2012</a></li> <li><a href='http://www.webmastersgallery.com/2012/07/'>July 2012</a></li> <li><a href='http://www.webmastersgallery.com/2012/06/'>June 2012</a></li> <li><a href='http://www.webmastersgallery.com/2012/01/'>January 2012</a></li> <li><a href='http://www.webmastersgallery.com/2011/11/'>November 2011</a></li> <li><a href='http://www.webmastersgallery.com/2011/06/'>June 2011</a></li> <li><a href='http://www.webmastersgallery.com/2011/03/'>March 2011</a></li> <li><a href='http://www.webmastersgallery.com/2011/02/'>February 2011</a></li> <li><a href='http://www.webmastersgallery.com/2010/11/'>November 2010</a></li> <li><a href='http://www.webmastersgallery.com/2010/09/'>September 2010</a></li> <li><a href='http://www.webmastersgallery.com/2010/07/'>July 2010</a></li> <li><a href='http://www.webmastersgallery.com/2010/06/'>June 2010</a></li> <li><a href='http://www.webmastersgallery.com/2010/05/'>May 2010</a></li> <li><a href='http://www.webmastersgallery.com/2010/02/'>February 2010</a></li> <li><a href='http://www.webmastersgallery.com/2009/07/'>July 2009</a></li> <li><a href='http://www.webmastersgallery.com/2009/06/'>June 2009</a></li> <li><a href='http://www.webmastersgallery.com/2009/05/'>May 2009</a></li> <li><a href='http://www.webmastersgallery.com/2009/04/'>April 2009</a></li> <li><a href='http://www.webmastersgallery.com/2009/03/'>March 2009</a></li> </ul> </div> <!-- meta --> <div class="widget"> <h3>Meta</h3> <ul> <li><a href="http://www.webmastersgallery.com/wp-login.php">Log in</a></li> </ul> </div> </div> <!-- sidebar south END --> </div> <!-- sidebar END --> <div class="fixed"></div> </div> <!-- content END --> <!-- footer START --> <div id="footer"> <a id="gotop" href="#" onclick="MGJS.goTop();return false;">Top</a> <a id="powered" href="http://wordpress.org/">WordPress</a> <div id="copyright"> Copyright © 2009-2018 Webmasters Gallery </div> <div id="themeinfo"> Theme by <a href="http://www.neoease.com/">NeoEase</a>. Valid <a href="http://validator.w3.org/check?uri=referer">XHTML 1.1</a> and <a href="http://jigsaw.w3.org/css-validator/check/referer?profile=css3">CSS 3</a>. </div> </div> <!-- footer END --> </div> <!-- container END --> </div> <!-- wrap END --> <script type='text/javascript' src='http://www.webmastersgallery.com/wp-content/plugins/akismet/_inc/form.js?ver=3.1.5'></script> </body> </html>