SUMMARY In Canada we have single-payer healthcare, where basic services are provided by private doctors, with the entire fee paid for by the government - mostly the provincial government. Although most services are free at point-of-use, there are several services that are not covered for which are typically paid for by private insurance companies, or out-of-pocket. This visualizatio examines the distribution of health care spending of both private and public funds. It also allows you to see the distribution of funding within each category of spending.
DESIGN I chose a stacked area chart because it's a good way of visualizing distributions and I wanted to see changes over time. I used colour as a visual encoding for different categories of spending. I also added labels.
However, I think it can be hard to determine an individual category's changes over time when it is stacked so I also wanted to add the ability to just see the area graph for one category of healthcare spending.
I began with just public and private filters to examine distribution of spending in each sector. But then, I realised the actual amount of money spent by private and public sectors varies greatly. So, I decided to add the extra ability to examine each category of spending individually to see how much of each service is funded by private funds and how much is funded by public funds.
I received feedback that adding in some sort of label to remind you of which layer you had clicked would be useful and so I added this.
What do you notice in the visualization? What questions do you have about the data? What relationships do you notice? What do you think is the main takeaway from this visualization? Is there something you don’t understand in the graphic?
FEEDBACK include all feedback you received from others on your visualization from the first sketch to the final visualization For each person that gives you feedback, add the person’s feedback to your README.md file in the Feedback section. As you improve and iterate on your visualization, update your code AND describe any changes in the Design section of the README.md file.
RESOURCES list any sources you consulted to create your visualization
xxxxxxxxxx
<html>
<head>
<title>Distribution of Healthcare Expenditure by Use of Funds</title>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8">
</head>
<body>
<h2>Healthcare Expenditure by Use and Source of Funds</h2>
<div class="clearance"></div>
<div id='main-wrapper'>
<div id='filters'>
<div class='demo' data-val="Public">
<span>Public</span>
</div>
<div class='demo current' data-val="Private">
<span>Private</span>
</div>
<div class='sep'> </div>
<div class='demo' data-val="Hospital">
<span>Hospital</span>
</div>
<div class='demo' data-val="Other Institutions">
<span>Other Institutions</span>
</div>
<div class='demo' data-val="Physicians">
<span>Physicians</span>
</div>
<div class='demo' data-val="Other Professionals">
<span>Other Professionals</span>
</div>
<div class='demo' data-val="Drugs">
<span>Drugs</span>
</div>
<div class='demo' data-val="Public Health">
<span>Public Health</span>
</div>
<div class='demo' data-val="Administration">
<span>Admin</span>
</div>
<div class='demo' data-val="Capital">
<span>Capital</span>
</div>
<div class='demo' data-val="Other Health Spending">
<span>Other</span>
</div>
</div>
<!--space between filter elements and chart-->
<div class="clearance"></div>
<div id="chart"></div>
</div>
<!--end of top wrapper box-->
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="colorbrewer.min.js"></script>
<script type="text/javascript">
//~~~~~USER SETTINGS~~~~~
var USER_FILTER = "Private";
var USER_SELECT = null;
//~~~~~MARGINS~~~~~
//define margin object according to d3.js conventions
//calculate width and height
var margin = {top: 10, right: 15, bottom: 30, left:40},
width = 1090 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
//~~~~~SCALES~~~~~
//define linear horizontal and vertical scales
//and colour scale for different categories of spending
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var colour = d3.scale.ordinal().range(colorbrewer.Set3[9]);
var colourtwo = d3.scale.ordinal().domain(["Private1", "Public1"]).range(colorbrewer.Set2[8]);
//~~~~~AXES~~~~~
var numFormatter = d3.format(".0f")
//define x-axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(function(d) { return numFormatter(d); });
//define y-axis, format as percentage eg. 100%
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(function(d) { return numFormatter(d) + "%"; });
//~~~~~CATEGORY METADATA~~~~~
//metadata for each category of spending
/*
var meta = {
"Hospitals": {"name": "Hospitals", "descrip": "Hospitals"},
"Other Institutions": {"name": "Other Institutions", "descrip": "Other Institutions"},
"Physicians": {"name": "Physicians", "descrip": "Physicians"},
"Other Professionals": {"name": "Other Professionals", "descrip": "Dental, Vision Care, Other"},
"Drugs": {"name": "Drugs", "descrip": "Prescriptions, Over the Counter, Personal Supplies"},
"Capital": {"name": "Capital", "descrip": "Capital"},
"Public Health": {"name": "Public Health", "descrip": "Public Health"},
"Administration": {"name": "Administration", "descrip": "Administration"},
"Other Health Spending": {"name": "Other Health Spending", "descrip": "Research, Other Goods, Other Services"},
}
*/
//~~~~~NESTING~~~~~
//in order to stack data it must be nested into arrays that each represent one category
//we want nested data to look like this:
/*[
{"key":"Hospital"
"values":[
{"use":"Hospital", "year":1975, "prop": 10.9, "sector":"Private"},
{"use":"Hospital", "year":1976, "prop": 11.7, "sector":"Private"}, ETC.
]
},
{"key":"Drugs"
"values":[
{"use":"Drugs", "year":1975, "prop": 31.7, "sector":"Private"},
{"use":"Drugs", "year":1976, "prop": 30.4, "sector":"Private"}, ETC.
]
}, ETC.
]*/
var nest = d3.nest()
.key(function (d) { return d.use; });
//~~~~~STACK LAYOUT~~~~~
//takes the nested data and computes baseline value (y0) i.e. sum of all previous category y values
//default offset is 0
//extract points array by returning "values" array from nested data
//x-coordinate accessor selects age
//y-coordinate accessor selects proportion of spending
var stack = d3.layout.stack()
.values(function(d) { return d.values; })
.x(function(d) { return d.age; })
.y(function(d) { return d.prop; });
//~~~~~CHART AREA GENERATOR~~~~~
//returned function generates path data for a polygon
//top line formed by using (x,y1) accessor functions
//bottom line formed by using (x,y0) accessor functions
//x accessor function is year
//y0 accessor function is baseline value computed from stack layout
//y1 accessor function is sum of baseline value from stack layout and proportion
var area = d3.svg.area()
.x(function(d) { return x(d.year); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
//~~~~~START SVG OBJECT~~~~~
//select chart ID element and append SVG element with specified margins
//append <g> element and move it into centre of SVG element
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//~~~~~LOAD DATA~~~~~
//accessor function (type) coerces strings into numbers
//callback function (draw) to draw chart
d3.csv("formatted_data.csv", type, draw)
function type(d) {
//set year and proportion of spending to numeric values
d.year = +d.year;
d.prop = +d.prop;
return d;
}
//~~~~~DRAW CHART~~~~~
function draw(error, data) {
if (error) throw error;
//~~~~~FILTER AND STACK~~~~~
//filter data by sector depending on user selection
var filtered = data.filter(function(d) { return d.sector === USER_FILTER; });
//call stack on filtered and nested data
var layers = stack(nest.entries(filtered));
//~~~~~SET DOMAINS~~~~~
//set domains for x and y scales
x.domain(d3.extent(filtered, function(d) { return d.year; }));
y.domain([0, d3.max(filtered, function(d) { return d.y0 + d.y; })]);
//~~~~~DATA JOIN STACKED AREA ELEMENTS~~~~~
//join data with layer class elements
//.data(layers) returns UPDATE selection = array of length 9 contains nothing
//.enter() returns ENTER selection = array of 9 objects with data attached
//.append("path) appends new <path> element as last child of each element in ENTER selection
//set class to class
//set id to the key i.e. category of spending
//set path description to path returned by area
//set fill to output range value from giving input domain to colour
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("id", function(d) { return d.key; })
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d) { return colour(d.key); });
//~~~~~ADD AXES~~~~~
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
//~~~~~DATA JOIN LAYER LABEL ELEMENTS~~~~~
//don't show "Public Health" label if user selects "Private" sector since spending is 0
var layer_label = svg.selectAll(".layerlabel")
.data(layers)
.enter().append("text")
.attr("class", "layerlabel")
.attr("transform", function (d) {
return "translate(" + x(2010) + "," + y(d.values[35].y0 + d.values[35].y/2) + ")"; })
.attr("dy", "0.5em")
.text(function (d) {
if (USER_FILTER === "Private" && d.key === "Public Health") {
return ""; }
else {
return d.key; }});
//~~~~~ADD FILTER BUTTON INTERACTIVITY~~~~~
//adds or removes an event listener (unnamed function) to each element in current selection
d3.selectAll("#filters .demo").on("click", function() {
d3.select(".demo.current").attr("class", "demo");
USER_FILTER = d3.select(this).attr("data-val");
d3.select(this).attr("class", "demo current");
if (USER_FILTER === "Public" || USER_FILTER === "Private") {
update();
} else {
update_two();
}
});
//~~~~~ADD LAYER INTERACTIVITY~~~~~
svg.selectAll(".layer").on("click", function(d,i) {
//if user has not clicked and clicks then set USER_SELECT to "id" of clicked on DOM element
if (USER_SELECT === null) {
USER_SELECT = d3.select(this).attr("id");
} else {
USER_SELECT = null;
}
update()
});
//~~~~~STUFF FOR WHEN ONE LAYER IS SELECTED~~~~~
//~~~~~UPDATE CHART~~~~~
function update() {
console.log(USER_FILTER);
filtered = data.filter(function(d) { return d.sector === USER_FILTER; });
if (USER_SELECT != null) {
//make all non-selected layers' prop = 0 so they don't show
filtered = filtered.map(function(d) {
if (d.use === USER_SELECT) {
//return selected layer object as is
return d;
} else {
return {
"year": d.year,
"use": d.use,
"sector": d.sector,
"prop": 0,
"y": d.y,
"y0": d.y0};
}
});
}
//call stack on filtered and nested data
var layers = stack(nest.entries(filtered));
//d3 transition on UPDATE selection of layers
d3.selectAll(".layer")
.data(layers)
.transition()
.duration(1000)
.attr("d", function(d) {
return area(d.values);
})
.style("fill", function(d) { return colour(d.key); });
//d3 transition on UPDATE selection of layer labels
d3.selectAll(".layerlabel")
.data(layers)
.transition()
.duration(1000)
.attr("transform", function (d) {
return "translate(" + x(2010) + "," + y(d.values[35].y0 + d.values[35].y/2) + ")"; })
.attr("dy", "0.5em")
.text(function (d) {
if (USER_FILTER === "Private" && d.key === "Public Health") {
return ""; }
else {
return d.key; }})
.style("opacity", function(){
if (USER_SELECT != null) {
return 0;
} else { return 1;}
});
}
//~~~~~UPDATE CHART SECOND~~~~~
function update_two() {
filtered = data.filter(function(d) { return d.sector === "Public"; });
//~~~~~CLEAR CHART~~~~~
//make all layers prop 0 so as to 'clear' the graph
filtered = filtered.map(function(d) { return {
"year": d.year,
"use": d.use,
"sector": d.sector,
"prop": 0,
"y": d.y,
"y0": d.y0}; });
//call stack on filtered and nested data
var layers = stack(nest.entries(filtered));
//d3 transition on UPDATE selection of layers
d3.selectAll(".layer")
.data(layers)
.transition()
.duration(1000)
.attr("d", function(d) {
return area(d.values);
});
//remove labels
d3.selectAll(".layerlabel")
.data(layers)
.transition()
.duration(1000)
.style("opacity", 0);
//~~~~~DRAW NEW CHART~~~~~
//use second data set that calculated the percentages of each funding source for each usage
filtered = data.filter(function(d) { return d.sector === "Private1" ||
d.sector === "Public1"; });
//filter to the selected use
filtered = filtered.filter(function(d) { return d.use === USER_FILTER; });
var nestbysector = d3.nest().key(function (d) { return d.sector; });
//call stack on filtered and nested data
var layers = stack(nestbysector.entries(filtered));
//d3 transition on UPDATE selection of layers
d3.selectAll(".layer")
.data(layers)
.transition()
.duration(1000)
.attr("d", function(d) {
return area(d.values);
})
.style("fill", function(d) { return colourtwo(d.key); });
d3.selectAll(".layerlabel")
.data(layers)
.transition()
.duration(1000)
.attr("transform", function (d) {
return "translate(" + x(2010) + "," + y(d.values[35].y0 + d.values[35].y/2) + ")"; })
.attr("dy", "0.5em")
.text(function (d) {
if (USER_FILTER === "Public Health" && d.key === "Private1") {
return ""; }
else { if (d.key === "Private1") { return "Private";
} else { return "Public"; }
}})
.style("opacity", function(){
if (USER_SELECT != null) {
return 0;
} else { return 1;}
});
}
}
</script>
</body>
</html>
Modified http://d3js.org/d3.v3.min.js to a secure url
https://d3js.org/d3.v3.min.js