Recently, I am working on Tencent Cloud big data visualization project, dealing with various bar charts, pie charts, area charts and so on every day.

Pie charts are one of the most common charts that visually show the proportion of items in a pile of data. This article attempts to show how to draw a pie chart in a browser.


The preparatory work

The data to be presented

This is the data of a poverty alleviation project somewhere

  • Rain plan :21

  • Financial poverty alleviation :25

  • Poverty alleviation by industry :70

  • Infrastructure :40

D3.js

This is an excellent JavaScript library, support < SVG > and

drawing, can simplify the drawing work involved in a large number of calculations, animation, can be called a drawing engine.


Let’s get started

Start by organizing the data into a code-friendly format

Const oriData = [{" x ":" the rain plan ", "y" : 20}, {" x ":" financial poverty alleviation ", "y" : 20}, {" x ":" industry poverty ", "y" : 70}, {" x ":" infrastructure ", "y" : 40}].Copy the code

Define canvas size

// Canvas size const [width, height] = [450, 350];Copy the code

Then use D3 to generate a < SVG > element and mount it below #main. This < SVG > will be used as the canvas for subsequent drawing.

Let SVG = d3.select("#main").append(" SVG ").attr("width", width).attr("height", height);Copy the code

Attr (“width”, width) and attr(“height”, height) apply to the < SVG > element. Instead of “#main” as originally selected.


A data item is represented by a sector, and the sum of the data items is a complete circle. So pie charts are actually pie charts that correspond to each data item. Each fan has a starting Angle and an ending Angle.

We need to figure out the starting and ending angles of each sector, and d3.js packages this work, called the layout.

Let pie = d3.pie().value(d => d.y); //.sort(null); DrawData = pie(oriData); drawData = pie(oriData);Copy the code

D3.pie () creates the layout,

.value(d => d.y) Sets the value filter for the layout

A typical chain call.


Let’s look at what the layout transformed data looks like

console.log(drawData);
Copy the code

Data: stores raw data

Index: 2 sort index, D3 pie layout defaults to invert by data size

StartAngle: 4.607669225265029 Indicates the starting Angle of the arc

EndAngle: 5.445427266222308 indicates the endAngle of an arc

The minimum Angle is 0, and the maximum Angle is 2π(approximately equal to 6.283185307179586 mo-mo, Mo-mo).

By default, the beginning of the index 0 arc is in the direction from 12, and the end of the index arc is in the direction of 12, and the two arcs are closed end to end

Value: 20 The value from the source data, because code d => d.y sets the data source to be the Y attribute

PadAngle :0 Indicates the Angle between adjacent arcs. This is not set, so it is 0


With the Angle data for each sector, you are ready to draw. There is no “sector” element available in < SVG >, so you need to define any path to draw the sector using the tag.

Using the tag to define a path is a difficult task. It requires multiple calls to moveto, lineto, closepath, etc., depending on the complexity of the path.

So we directly use the “arc generator” provided by D3.js to generate paths.

// let radius = math. min(width, height) * 0.8/2; Let arc = d3.arc().innerradius (0).outerradius (radius);Copy the code

Same chain call, you pass in the inner diameter and the outer diameter, pass the inner diameter 0 and you get the pie graph, pass the non-0 and you get the ring graph, which is easy to understand.

Arc is a Curryback function that takes objects with startAngle, endAngle, and padAngle attributes and returns the d attribute .


Now that we are ready, we need to draw onto < SVG >.

But before we do that, we need to prepare another

element to act as a container for the of each arc. Why we do this will be revealed later.

let pathParent = svg.append("g");
Copy the code

It then performs a long sequence of operations

pathParent.selectAll("path") .data(drawData) .enter() .append("path") .attr("d", oneData => arc(oneData)); // Call arc generator to get the pathCopy the code

Here comes the mystery

SelectAll (“path”) returns all elements that don’t exist yet and how many may exist.

.data(drawData) binds the pre-prepared data that has been converted by the layout. Since the data is bound, the object returned by data knows how many are required.

The first two lines of.enter() indicate that these elements do not yet exist, but we know how many are required. Enter () selects these empty elements that do not yet exist, so enter is used to select elements based on the number of entries in the data. These correspond to elements that already exist and those that need to be deleted, but of course they are not used here. See here for details.

.append(“path”) selects these not yet existing , which will have to be completed to make them really exist.

Attr (“d”, oneData => arc(oneData)) passes through these elements and assigns them the D attribute, whose value is the result of calling the arc generator.


At this point, the pie charts are made up of sectors. Take a look at:

And you get a result like this, yeah.

As you can see, we did not set the coordinate offset and fill color of each path.

Let’s start with coordinate offsets.


“Position is relative” observe the position of < SVG > and each on the page.

The paths are all plotted, but only the fourth quadrant of the pie chart is shown.

The reason is that in general 2d plane drawing, we take (0,0) coordinate as the origin, and in order to simplify the calculation complexity, we directly calculate the relative coordinate of the path, deliberately do not bring the absolute coordinate into the calculation, and then achieve the displacement through the displacement canvas or container.

A parent

element of all was prepared to move it to the middle of the canvas.

pathParent.attr("transform", `translate(${width / 2}, ${height / 2})`);
Copy the code

The pie chart is already working.


Let’s go ahead and color it

Set the color scale. For the pie chart, the function of this scale is to obtain a corresponding color value according to the serial number of a section on the pie.

let colorScale = d3.scaleOrdinal().domain(d3.range(0, oriData.length)).range(d3.schemeCategory20c);
Copy the code

Set the fill property at the drawing

pathParent .selectAll("path") .data(drawData) .enter() .append("path") .attr("fill", Function (d) {// Set the fill color return colorScale(d.index); }) .attr("d", d => arc(d));Copy the code

Now that the pie chart is drawn, add some text labels.

Let sum = d3.sum(oriData, d => d.y); Let textParent = svg.append("g"); let textParent = svG.append ("g"); textParent.attr("transform", `translate(${width / 2}, ${height / 2})`); Textparent.selectall ("text").data(drawData).enter().append("text").attr("transform") Function (d) {return "translate(" + arc.centroid(d) + ")"; }) .attr("text-anchor", "middle") .attr("font-size", "10px").text(function(d) {return (d.data.y/sum * 100).tofixed (2) + "%"; });Copy the code

Complete the chart

So far, the basic cake is well drawn.


I still have a few things to do,

Mouse and sector interaction, click, over, etc.

Text labels are not friendly, many pie charts have realized labels connected by lines outside the pie, and connecting lines alone will involve a lot of problems to deal with. For example, when the pie charts implemented under some algorithms correspond to different data, the lines may intersect with the fan, which is very ugly, or the connecting lines are required to be only on both sides of the pie, etc.

These questions will be sent later.