tomgp
5/20/2016 - 2:00 PM

proportional pie charts

proportional pie charts

[{"type":"region","region_id":"E12000001","electorate":2093713,"turnout_abs":1118356,"leave_abs":570812,"remain_abs":547544,"turnout_pct":53.414961840519695,"leave_pct":51.04027697799269,"remain_pct":48.9597230220073},{"type":"region","region_id":"E12000002","electorate":5618117,"turnout_abs":2792986,"leave_abs":1519530,"remain_abs":1273456,"turnout_pct":49.713916602306426,"leave_pct":54.40521363157566,"remain_pct":45.59478636842434},{"type":"region","region_id":"E12000003","electorate":4219056,"turnout_abs":2086635,"leave_abs":1023050,"remain_abs":1063585,"turnout_pct":49.45739046838914,"leave_pct":49.02869931732191,"remain_pct":50.97130068267809},{"type":"region","region_id":"E12000004","electorate":3671679,"turnout_abs":1852882,"leave_abs":931743,"remain_abs":921139,"turnout_pct":50.46416094653154,"leave_pct":50.28614882113378,"remain_pct":49.71385117886622},{"type":"region","region_id":"E12000005","electorate":4476290,"turnout_abs":2253921,"leave_abs":1123990,"remain_abs":1129931,"turnout_pct":50.35243471714299,"leave_pct":49.8682074482646,"remain_pct":50.1317925517354},{"type":"region","region_id":"E12000006","electorate":4730846,"turnout_abs":2341720,"leave_abs":1264030,"remain_abs":1077690,"turnout_pct":49.49896910615987,"leave_pct":53.97869941752216,"remain_pct":46.021300582477835},{"type":"region","region_id":"E12000007","electorate":6618717,"turnout_abs":3323458,"leave_abs":1741819,"remain_abs":1581639,"turnout_pct":50.21302466928258,"leave_pct":52.40983939017734,"remain_pct":47.59016060982266},{"type":"region","region_id":"E12000008","electorate":6969602,"turnout_abs":3483603,"leave_abs":1764413,"remain_abs":1719190,"turnout_pct":49.982811070129976,"leave_pct":50.64908372165255,"remain_pct":49.35091627834745},{"type":"region","region_id":"E12000009","electorate":4346897,"turnout_abs":2218818,"leave_abs":1053545,"remain_abs":1165273,"turnout_pct":51.043721532854356,"leave_pct":47.482263078810426,"remain_pct":52.517736921189574},{"type":"region","region_id":"N07000001","electorate":1407337,"turnout_abs":831299,"leave_abs":449716,"remain_abs":381583,"turnout_pct":59.06893658022207,"leave_pct":54.097983998537224,"remain_pct":45.90201600146277},{"type":"region","region_id":"S12000001","electorate":5285228,"turnout_abs":2664537,"leave_abs":1364605,"remain_abs":1299932,"turnout_pct":50.41479762084058,"leave_pct":51.21358795167791,"remain_pct":48.78641204832209},{"type":"region","region_id":"W08000001","electorate":1491616,"turnout_abs":754660,"leave_abs":365495,"remain_abs":389165,"turnout_pct":50.59345032501663,"leave_pct":48.431744096679296,"remain_pct":51.568255903320704}]

An idea for showing vote distribution, turnout and electorate across a different areas. The outer circle's area represents the total electorate the pie chart area is proportional to the numebr of votes cast so turnout can be infered . Notwithstanding problems of comparing areas and a general distaste for piecharts.

function proportionalPie(){
	var pie = d3.layout.pie().sort(null);

	var radiusScale = d3.scale.sqrt()
      .domain([0, 3000000])
      .range([0, 50]);

    var tickInterval = 1000000;
	var ticks = null;

	var classer = function(d,i){
		return 'segment-' + i;
	}

	var extentMarker = false; // how big could this pie have been?
	var extentAccessor = function(d){ return d.extent; }
	function chart(parent){
		parent.each(function(d){
			var arc = d3.svg.arc()
				.innerRadius(0)
				.outerRadius(radiusScale(d.total))	//based on d.total

			var anchor = d3.select(this);
			var pieData = pie(d.values);
			var labelData = pieData.map(function(p){
				return {
					angle: ((p.endAngle + p.startAngle)/2) - Math.PI/2,
					value: Math.round( (p.data/d.total)*100 )
				}
			});

			anchor.selectAll('path')
				.data(pieData)
				.enter()
					.append('path')
					.attr('d', arc)
					.attr('class', classer)

			if( tickInterval === null || ticks === null){
				var tickMarks = [];
			}else{
				tickMarks = d3.range(tickInterval, d.total + tickInterval ,tickInterval );
			}

			if(extentMarker){
				anchor.append('circle')
					.attr('class','extent')
					.attr('r',function(d){
						return radiusScale( extentAccessor(d) );
					})
					
			}

			anchor.selectAll('circle.tick') //the axes circle
				.data(tickMarks)
				.enter()
					.append('circle')
					.attr('class','tick')
					.attr('r',radiusScale);
		})
	}
	
	chart.extentAccessor = function(f){
		extentAccessor = f;
		return chart;
	}

	chart.extentMarker = function(x){
		extentMarker = x;
		return chart;
	}

	chart.totalAccessor = function(f){
		if(f==undefined){
			totalAccessor = function(d){ return d.total }
		}else{
			totalAccessor = f;
		}
		return chart;
	}
	
	chart.valuesAccessor = function(f){
		if(f==undefined){
			valuesAccessor = function(d){ return d.values }
		}else{
			valuesAccessor = f;
		}
		return chart;
	}

	chart.ticks = function(a){ //expects an array
		if(a==undefined) return ticks;
        ticks = a;
        tickInterval = null;
        return chart;
	};

	chart.tickInterval = function(x){
		if(x==undefined) return tickInterval;
		tickInterval = x;
        ticks = null;
		return chart;
	};

	chart.maxValue = function(x){
		if(x==undefined) return radiusScale.domain()[1];
		var dom = radiusScale.domain();
		dom[1] = x;
		radiusScale.domain(dom);
		return chart;
	};

	chart.maxRadius = function(x){
		if(x==undefined) return radiusScale.range()[1];
		var range = radiusScale.range();
		range[1] = x;
		radiusScale.range(range);
		return chart;
	};

	chart.radiusScale = function(x){
		return radiusScale(x);
	};

	chart.classer = function(f){ //takes a function
		if(!f) return classer;
		classer = f;
		return chart;
	};

	return chart;
}
<!doctype html>
<html lang="en-GB" class="core">
<head>
    <meta charset="utf-8">
    <script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script src="proportional-pie.js"></script>
</head>
<body>
    <main class="article" role="main">
        <h1 class="article__headline--mega">Proportional Pies!</h1>
    </main>
    <div class="chart"></div>
</body>
<style>
body{
  font-family: sans-serif;
}
.segment-0{
    fill:#FF9999;
}
.segment-1{
    fill:#9999FF;
}
.extent{
    fill:none;
    stroke:#000;
    stroke-width:3;
    stroke-dasharray: 5 2;
}
.pie-container{
    display: inline-block;
    margin: 10px;
}

</style>
<script>

    
    d3.json('regional.json',function(regionaldata){
        
        var pie = proportionalPie()
            .maxRadius(45)
            .maxValue(d3.max(regionaldata, function(d){ return d.electorate; }))
            .extentMarker(true)
            .extentAccessor(function(d){
                return d.electorate;
            });
        
        var regionaldata = regionaldata.map(function(d){
            d.values = [d.remain_pct,d.leave_pct];
            d.total = d.turnout_abs;
            d.name = '-'+d.region_id+'-'; //put a proper name in here
            return d;
        });
        
        console.log(regionaldata);
        
        d3.select('.chart')
            .selectAll('div.pie-container')
                .data(regionaldata)
                .enter()
                    .append('div')
                .attr('class','pie-container')
                    .call(function(parent){
                        parent.append('h3')
                            .text(function(d){ return d.name });
               
                        parent.append('svg')
                                .attr('class','pie-chart')
                                .attr('width', 100)
                                .attr('height', 100)
                                .attr('viewBox','0 0 100 100')
                            .append('g')
                                .attr('transform','translate(50,50)')
                            .call(pie);
                            
                        parent.append('div')
                            .html(function(d){
                                return 'L <span class="leave-label">'+Math.round(d.leave_pct)+'%</span> R <span class="remain-label">'+Math.round(d.remain_pct)+'%</span>. Turnout ' + Math.round(d.turnout_pct) +'%';
                            })
                    })
    })

</script>


</html>