Last active
December 27, 2017 11:44
-
-
Save Zhenmao/a345870bfa32e1434b7564f9ed9d69bb to your computer and use it in GitHub Desktop.
Line donut chart with text in the center
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width"> | |
<title>Line Donut Chart</title> | |
<!-- D3 --> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<!-- Google Font --> | |
<link href="https://fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet"> | |
<style> | |
body { | |
background-color: #F5F6F7; | |
font-family: 'Roboto', sans-serif; | |
} | |
/***********************************************************/ | |
/**** Make the chart container fill the page using CSS. ****/ | |
/***********************************************************/ | |
#chart { | |
position: fixed; | |
left: 0px; | |
right: 0px; | |
top: 0px; | |
bottom: 0px; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="chart"></div> | |
<script> | |
////////////////////////////////////////////////////////////////// | |
//// Data //////////////////////////////////////////////////////// | |
////////////////////////////////////////////////////////////////// | |
var donutData = [ | |
{id: 1, value: 500, color: '#C961A2'}, | |
{id: 2, value: 375, color: '#2978C4'}, | |
{id: 3, value: 125, color: '#4EE7B3'}, | |
]; | |
var total = 300000; | |
var revolving = 0.68; | |
var fixed = 0.32; | |
////////////////////////////////////////////////////////////////// | |
//// Chart generator ///////////////////////////////////////////// | |
////////////////////////////////////////////////////////////////// | |
// make sure d3.js is loaded | |
// Chart appends an SVG inside a div with id 'chart' | |
// Chart expects the following input arguments: | |
// donutData: data to draw the donut slices | |
// total: the total amount (e.g. 300000) | |
// revolving: the revolving percent (e.g. 0.62) | |
// fixed: the fixed percent (e.g. 0.38) | |
var Chart = (function(donutData, total, revolving, fixed) { | |
var margin, width, height, chartRadius, radius, circleRadius, fontSize, pie, arc, chartDiv, svg, pieContainer, label; | |
// init only run once when setting up the chart | |
function init() { | |
// starting angle for the first slice of donut chart | |
var startAngle = - 1/8 * 2 * Math.PI; | |
// small white circle radius | |
circleRadius = 12; | |
pie = d3.pie() | |
.sort(null) | |
.startAngle(startAngle) | |
.endAngle(2 * Math.PI + startAngle) | |
.value(function(d) { | |
return d.value; | |
}); | |
chartDiv = document.getElementById('chart'); | |
svg = d3.select(chartDiv).append('svg'); | |
// circle dropshadow definition | |
var filter = svg.append('defs') | |
.append('filter') | |
.attr('id', 'drop-shadow') | |
.attr('height', '200%') | |
.attr('width', '200%'); | |
filter.append('feGaussianBlur') | |
.attr('in', 'SourceAlpha') | |
.attr('stdDeviation', 1); // shadow blur | |
filter.append('feOffset') | |
.attr('in', 'blur') | |
.attr('dx', 1) // shadow offset | |
.attr('dy', 2) // shadow offset | |
.attr('result', 'offsetBlur'); | |
var feComponentTransfer = filter.append('feComponentTransfer'); | |
feComponentTransfer.append('feFuncA') | |
.attr('type', 'linear') | |
.attr('slope', 0.2); // shadow color darknewss | |
var feMerge = filter.append('feMerge'); | |
feMerge.append('feMergeNode'); | |
feMerge.append('feMergeNode') | |
.attr('in', 'SourceGraphic'); | |
renderChart(); | |
} | |
// renderChart runs every time window resizes | |
function renderChart() { | |
updateDimensions(chartDiv.clientWidth, chartDiv.clientHeight); | |
// radius of the donut ring | |
radius = chartRadius * 0.9; | |
svg.attr('width', width + margin.left + margin.right) | |
.attr('height', height + margin.top + margin.bottom); | |
d3.selectAll("g > *").remove(); | |
svg.append('g') | |
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
pieContainer = svg.append('g') | |
.attr('transform', 'translate(' + width/2 + ',' + height/2 + ')'); | |
arc = d3.arc() | |
.outerRadius(radius) | |
.innerRadius(radius); | |
pieContainer.selectAll('.arc') | |
.data(pie(donutData)) | |
.enter().append('path') | |
.attr('class', 'arc') | |
.attr('d', function(d) { | |
return arc(d); | |
}) | |
.attr('stroke', function(d) { | |
return d.data.color; | |
}) | |
.attr('stroke-width', 3); | |
pieContainer.selectAll('.circle') | |
.data(pie(donutData)) | |
.enter().append('circle') | |
.attr('class', 'circle') | |
.attr('cx', function(d) { | |
return getCirclePos(d).cx; | |
}) | |
.attr('cy', function(d) { | |
return getCirclePos(d).cy; | |
}) | |
.attr('r', circleRadius) | |
.attr('fill', '#fff') | |
.attr('filter', 'url(#drop-shadow)'); | |
label = pieContainer.append('text') | |
.attr('text-anchor', 'middle'); | |
label.append('tspan') | |
.attr('id', 'total-title') | |
.attr('x', 0) | |
.attr('y', -radius/3) | |
.attr('font-size', 1.5 * fontSize) | |
.text('Total Amount Requested'); | |
label.append('tspan') | |
.attr('id', 'total') | |
.attr('x', 0) | |
.attr('y', -radius/3 + 3 * fontSize) | |
.attr('font-size', 2.5 * fontSize) | |
.text('$' + d3.format(',')(total)); | |
label.append('tspan') | |
.attr('id', 'revolving-title') | |
.attr('x', -radius/3) | |
.attr('y', radius/3 + 2 * fontSize) | |
.attr('font-size', 1 * fontSize) | |
.text('Revolving'); | |
label.append('tspan') | |
.attr('id', 'revolving') | |
.attr('x', -radius/3) | |
.attr('y', radius/3) | |
.attr('font-size', 2 * fontSize) | |
.text(Math.round(revolving * 100) + '%'); | |
label.append('tspan') | |
.attr('id', 'fixed-title') | |
.attr('x', radius/3) | |
.attr('y', radius/3 + 2 * fontSize) | |
.attr('font-size', 1 * fontSize) | |
.text('Fixed'); | |
label.append('tspan') | |
.attr('id', 'fixed') | |
.attr('x', radius/3) | |
.attr('y', radius/3) | |
.attr('font-size', 2 * fontSize) | |
.text(Math.round(fixed * 100) + '%'); | |
} | |
function updateDimensions(clientWidth, clientHeight) { | |
margin = {top: 10, bottom: 10, right: 10, left: 10}; | |
width = Math.max(clientWidth - margin.left - margin.right, 240); | |
height = Math.max(clientHeight - margin.top - margin.bottom, 240); | |
chartRadius = Math.min(width, height) / 2; | |
fontSize = chartRadius > 200 ? 16 : 12; | |
} | |
function getCirclePos(d) { | |
var theta = d.startAngle - 1/2 * Math.PI, | |
r = radius; | |
return {cx: r * Math.cos(theta), cy: r * Math.sin(theta)}; | |
} | |
init(); | |
return { | |
render: renderChart | |
}; | |
})(donutData, total, revolving, fixed); | |
////////////////////////////////////////////////////////////////// | |
//// Listener for resizing /////////////////////////////////////// | |
////////////////////////////////////////////////////////////////// | |
// Resize chart when window size changes | |
window.addEventListener('resize', Chart.render); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment