|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
|
|
h2.title { |
|
color: black; |
|
text-align: center; |
|
} |
|
|
|
.categories { |
|
fill: none; |
|
stroke: #fff; |
|
stroke-linejoin: round; |
|
} |
|
|
|
.categories-choropleth { |
|
fill: #ccc; |
|
} |
|
|
|
#tooltip-container { |
|
position: absolute; |
|
background-color: #fff; |
|
color: #000; |
|
padding: 10px; |
|
border: 1px solid; |
|
display: none; |
|
} |
|
|
|
#canvas svg { |
|
border: 0px; |
|
} |
|
|
|
.tooltip_key { |
|
font-weight: bold; |
|
} |
|
|
|
.tooltip_value { |
|
margin-left: 20px; |
|
float: right; |
|
} |
|
|
|
.x-axis { |
|
fill: #000; |
|
} |
|
|
|
.chart { |
|
background: #fff; |
|
margin: 5px; |
|
} |
|
|
|
.chart .category-bar { |
|
stroke: white; |
|
fill: steelblue; |
|
} |
|
|
|
.chart text.name { |
|
fill: #000; |
|
} |
|
|
|
.chart line { |
|
stroke: #c1c1c1; |
|
} |
|
|
|
.chart .rule { |
|
fill: #000; |
|
} |
|
|
|
.main-category-text { |
|
fill: #FF4A4A; |
|
} |
|
|
|
.main-category-bar { |
|
stroke: #FF4A4A; |
|
stroke-width: 2px; |
|
} |
|
|
|
path { stroke: #fff; } |
|
path:hover { opacity:0.9; } |
|
rect:hover { fill:steelblue; } |
|
.axis { font: 10px sans-serif; } |
|
.legend tr{ border-bottom:1px solid grey; } |
|
.legend tr:first-child{ border-top:1px solid grey; } |
|
|
|
.axis path, |
|
.axis line { |
|
fill: none; |
|
stroke: #000; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
.x.axis path { display: none; } |
|
.legend{ |
|
margin-bottom:76px; |
|
display:inline-block; |
|
border-collapse: collapse; |
|
border-spacing: 0px; |
|
} |
|
.legend td{ |
|
padding:4px 5px; |
|
vertical-align:bottom; |
|
} |
|
.legendFreq, .legendPerc{ |
|
align:right; |
|
width:50px; |
|
} |
|
|
|
</style> |
|
<body><h2 text="Healthcare Average Covered Charges (in thousands of dollars)" class="title"></h2></body> |
|
<body id="dashboard"></body> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.20/topojson.min.js"></script> |
|
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> |
|
<script type="text/javascript"> |
|
|
|
d3.select("body") |
|
.append("h2") |
|
.text("Healthcare Average Covered Charges (in thousands of dollars)") |
|
.attr('class', 'title'); |
|
|
|
function dashboard(id, fData){ |
|
|
|
var width = 1060, height = 500; |
|
|
|
var barColor = 'lightblue'; |
|
function segColor(c){ return {low:"#84d561", mid:"#e9916c",high:"#bc7fd1"}[c]; } |
|
|
|
// compute total for each state. |
|
fData.forEach(function(d){d.total=d.freq.low+d.freq.mid+d.freq.high;}); |
|
|
|
// function to handle histogram. |
|
function histoGram(fD){ |
|
var hG={}, hGDim = {t: 60, r: 0, b: 30, l: 0}; |
|
hGDim.w = 500 - hGDim.l - hGDim.r, |
|
hGDim.h = 300 - hGDim.t - hGDim.b; |
|
|
|
//create svg for histogram. |
|
var hGsvg = d3.select(id).append("svg") |
|
.attr("width", hGDim.w + hGDim.l + hGDim.r) |
|
.attr("height", hGDim.h + hGDim.t + hGDim.b).append("g") |
|
.attr("transform", "translate(" + hGDim.l + "," + hGDim.t + ")"); |
|
|
|
// create function for x-axis mapping. |
|
var x = d3.scale.ordinal().rangeRoundBands([0, hGDim.w], 0.1) |
|
.domain(fD.map(function(d) { return d[0]; })); |
|
|
|
// Add x-axis to the histogram svg. |
|
hGsvg.append("g").attr("class", "x axis") |
|
.attr("transform", "translate(0," + hGDim.h + ")") |
|
.call(d3.svg.axis().scale(x).orient("bottom")); |
|
|
|
// Create function for y-axis map. |
|
var y = d3.scale.linear().range([hGDim.h, 0]) |
|
.domain([0, d3.max(fD, function(d) { return d[1]; })]); |
|
|
|
// Create bars for histogram to contain rectangles and freq labels. |
|
var bars = hGsvg.selectAll(".bar").data(fD).enter() |
|
.append("g").attr("class", "bar"); |
|
|
|
//create the rectangles. |
|
bars.append("rect") |
|
.attr("x", function(d) { return x(d[0]); }) |
|
.attr("y", function(d) { return y(d[1]); }) |
|
.attr("width", x.rangeBand()) |
|
.attr("height", function(d) { return hGDim.h - y(d[1]); }) |
|
.attr('fill',barColor) |
|
.on("mouseover",mouseover)// mouseover is defined below. |
|
.on("mouseout",mouseout);// mouseout is defined below. |
|
|
|
//Create the frequency labels above the rectangles. |
|
bars.append("text").text(function(d){ return d3.format(",")(d[1])}) |
|
.attr("x", function(d) { return x(d[0])+x.rangeBand()/2; }) |
|
.attr("y", function(d) { return y(d[1])-5; }) |
|
.attr("text-anchor", "middle"); |
|
|
|
function mouseover(d){ // utility function to be called on mouseover. |
|
// filter for selected state. |
|
var st = fData.filter(function(s){ return s.State == d[0];})[0], |
|
nD = d3.keys(st.freq).map(function(s){ return {type:s, freq:st.freq[s]};}); |
|
|
|
// call update functions of pie-chart and legend. |
|
pC.update(nD); |
|
leg.update(nD); |
|
} |
|
|
|
function mouseout(d){ // utility function to be called on mouseout. |
|
// reset the pie-chart and legend. |
|
pC.update(tF); |
|
leg.update(tF); |
|
} |
|
|
|
// create function to update the bars. This will be used by pie-chart. |
|
hG.update = function(nD, color){ |
|
// update the domain of the y-axis map to reflect change in frequencies. |
|
y.domain([0, d3.max(nD, function(d) { return d[1]; })]); |
|
|
|
// Attach the new data to the bars. |
|
var bars = hGsvg.selectAll(".bar").data(nD); |
|
|
|
// transition the height and color of rectangles. |
|
bars.select("rect").transition().duration(500) |
|
.attr("y", function(d) {return y(d[1]); }) |
|
.attr("height", function(d) { return hGDim.h - y(d[1]); }) |
|
.attr("fill", color); |
|
|
|
// transition the frequency labels location and change value. |
|
bars.select("text").transition().duration(500) |
|
.text(function(d){ return d3.format(",")(d[1])}) |
|
.attr("y", function(d) {return y(d[1])-5; }); |
|
} |
|
return hG; |
|
} |
|
|
|
// function to handle pieChart. |
|
function pieChart(pD){ |
|
var pC ={}, pieDim ={w:250, h: 250}; |
|
pieDim.r = Math.min(pieDim.w, pieDim.h) / 2; |
|
|
|
// create svg for pie chart. |
|
var piesvg = d3.select(id).append("svg") |
|
.attr("width", pieDim.w).attr("height", pieDim.h).append("g") |
|
.attr("transform", "translate("+pieDim.w/2+","+pieDim.h/2+")"); |
|
|
|
// create function to draw the arcs of the pie slices. |
|
var arc = d3.svg.arc().outerRadius(pieDim.r - 10).innerRadius(0); |
|
|
|
// create a function to compute the pie slice angles. |
|
var pie = d3.layout.pie().sort(null).value(function(d) { return d.freq; }); |
|
|
|
// Draw the pie slices. |
|
piesvg.selectAll("path").data(pie(pD)).enter().append("path").attr("d", arc) |
|
.each(function(d) { this._current = d; }) |
|
.style("fill", function(d) { return segColor(d.data.type); }) |
|
.on("mouseover",mouseover).on("mouseout",mouseout); |
|
|
|
// create function to update pie-chart. This will be used by histogram. |
|
pC.update = function(nD){ |
|
piesvg.selectAll("path").data(pie(nD)).transition().duration(500) |
|
.attrTween("d", arcTween); |
|
} |
|
// Utility function to be called on mouseover a pie slice. |
|
function mouseover(d){ |
|
// call the update function of histogram with new data. |
|
hG.update(fData.map(function(v){ |
|
return [v.State,v.freq[d.data.type]];}),segColor(d.data.type)); |
|
} |
|
//Utility function to be called on mouseout a pie slice. |
|
function mouseout(d){ |
|
// call the update function of histogram with all data. |
|
hG.update(fData.map(function(v){ |
|
return [v.State,v.total];}), barColor); |
|
} |
|
// Animating the pie-slice requiring a custom function which specifies |
|
// how the intermediate paths should be drawn. |
|
function arcTween(a) { |
|
var i = d3.interpolate(this._current, a); |
|
this._current = i(0); |
|
return function(t) { return arc(i(t)); }; |
|
} |
|
return pC; |
|
} |
|
|
|
// function to handle legend. |
|
function legend(lD){ |
|
var leg = {}; |
|
|
|
// create table for legend. |
|
var legend = d3.select(id).append("table").attr('class','legend'); |
|
|
|
// create one row per segment. |
|
var tr = legend.append("tbody").selectAll("tr").data(lD).enter().append("tr"); |
|
|
|
// create the first column for each segment. |
|
tr.append("td").append("svg").attr("width", '16').attr("height", '16').append("rect") |
|
.attr("width", '16').attr("height", '16') |
|
.attr("fill",function(d){ return segColor(d.type); }); |
|
|
|
// create the second column for each segment. |
|
tr.append("td").text(function(d){ return d.type;}); |
|
|
|
// create the third column for each segment. |
|
tr.append("td").attr("class",'legendFreq') |
|
.text(function(d) { return d3.format(",")(d.freq); }); |
|
|
|
// create the fourth column for each segment. |
|
tr.append("td").attr("class",'legendPerc') |
|
.text(function(d){ return getLegend(d,lD);}); |
|
|
|
// Utility function to be used to update the legend. |
|
leg.update = function(nD){ |
|
// update the data attached to the row elements. |
|
var l = legend.select("tbody").selectAll("tr").data(nD); |
|
|
|
// update the frequencies. |
|
l.select(".legendFreq").text(function(d){ return d3.format(",")(d.freq);}); |
|
|
|
// update the percentage column. |
|
l.select(".legendPerc").text(function(d){ return getLegend(d,nD);}); |
|
} |
|
|
|
function getLegend(d,aD){ // Utility function to compute percentage. |
|
return d3.format("%")(d.freq/d3.sum(aD.map(function(v){ return v.freq; }))); |
|
} |
|
|
|
return leg; |
|
} |
|
|
|
// calculate total frequency by segment for all state. |
|
var tF = ['low','mid','high'].map(function(d) { |
|
return {type:d, freq: d3.sum(fData.map(function(t) { return t.freq[d]; }))}; |
|
}); |
|
|
|
// calculate total frequency by state for all segment. |
|
var sF = fData.map(function(d){return [d.State, d.total];}); |
|
|
|
var hG = histoGram(sF), // create the histogram. |
|
pC = pieChart(tF), // create the pie-chart. |
|
leg= legend(tF); // create the legend. |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function draw(data) { |
|
// "use strict"; |
|
// console.log(data) |
|
|
|
var WIDTH = 800, HEIGHT = 400; |
|
|
|
var COLOR_COUNTS = 9; |
|
|
|
var SCALE = 0.7; |
|
|
|
var config = { |
|
"main_category": "Washington", |
|
"avg_category": "Nation Average", |
|
"color1": '#c3e2ff', |
|
"color2": '#08306B', |
|
"stateDataColumn": "provider_state", |
|
"valueDataColumn": "med_average_covered_charges" |
|
}; |
|
|
|
var MAIN_CATEGORY = config.mainCategory; |
|
var AVG_CATEGORY = config.averageCategory; |
|
|
|
function Interpolate(start, end, steps, count) { |
|
var s = start, |
|
e = end, |
|
final = s + (((e - s) / steps) * count); |
|
return Math.floor(final); |
|
}; |
|
|
|
function Color(_r, _g, _b) { |
|
var r, g, b; |
|
var setColors = function(_r, _g, _b) { |
|
r = _r; |
|
g = _g; |
|
b = _b; |
|
}; |
|
|
|
setColors(_r, _g, _b); |
|
this.getColors = function() { |
|
var colors = { |
|
r: r, |
|
g: g, |
|
b: b |
|
}; |
|
return colors; |
|
}; |
|
}; |
|
|
|
function hexToRgb(hex) { |
|
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); |
|
return result ? { |
|
r: parseInt(result[1], 16), |
|
g: parseInt(result[2], 16), |
|
b: parseInt(result[3], 16) |
|
} : null; |
|
}; |
|
|
|
var COLOR_FIRST = config.color1, COLOR_LAST = config.color2; |
|
|
|
var rgb = hexToRgb(COLOR_FIRST); |
|
|
|
var COLOR_START = new Color(rgb.r, rgb.g, rgb.b); |
|
|
|
rgb = hexToRgb(COLOR_LAST); |
|
var COLOR_END = new Color(rgb.r, rgb.g, rgb.b); |
|
|
|
var MAP_CATEGORY = config.stateDataColumn; |
|
var MAP_VALUE = config.valueDataColumn; |
|
|
|
var width = WIDTH, |
|
height = HEIGHT; |
|
|
|
var valueById = d3.map(); |
|
|
|
var startColors = COLOR_START.getColors(), |
|
endColors = COLOR_END.getColors(); |
|
|
|
var colors = []; |
|
|
|
for (var i = 0; i < COLOR_COUNTS; i++) { |
|
var r = Interpolate(startColors.r, endColors.r, COLOR_COUNTS, i); |
|
var g = Interpolate(startColors.g, endColors.g, COLOR_COUNTS, i); |
|
var b = Interpolate(startColors.b, endColors.b, COLOR_COUNTS, i); |
|
colors.push(new Color(r, g, b)); |
|
}; |
|
|
|
var quantize = d3.scale.quantize() |
|
.domain([0, 1.0]) |
|
.range(d3.range(COLOR_COUNTS).map(function(i) { return i })); |
|
|
|
var path = d3.geo.path(); |
|
|
|
var svg = d3.select("#canvas-svg").append("svg") |
|
.attr("width", width) |
|
.attr("height", height); |
|
|
|
d3.tsv("https://s3-us-west-2.amazonaws.com/vida-public/geo/us-state-names.tsv", function(error, names) { |
|
// console.log(names); |
|
|
|
name_id_map = {}; |
|
id_name_map = {}; |
|
|
|
for (var i = 0; i < names.length; i++) { |
|
name_id_map[names[i].name] = names[i].id; |
|
id_name_map[names[i].id] = names[i].name; |
|
}; |
|
console.log(name_id_map); |
|
console.log(id_name_map); |
|
|
|
data.forEach(function(d) { |
|
var id = name_id_map[d[MAP_CATEGORY]]; |
|
valueById.set(id, +d[MAP_VALUE]); |
|
}); |
|
|
|
quantize.domain([d3.min(data, function(d){ return +d[MAP_VALUE] }), |
|
d3.max(data, function(d){ return +d[MAP_VALUE] })]); |
|
|
|
|
|
|
|
function makeMap(us) { |
|
svg.append("g") |
|
.attr("class", "categories-choropleth") |
|
.selectAll("path") |
|
.data(topojson.feature(us, us.objects.states).features) |
|
.enter().append("path") |
|
.attr("transform", "scale(" + SCALE + ")") |
|
.style("fill", function(d) { |
|
if (valueById.get(d.id)) { |
|
var i = quantize(valueById.get(d.id)); |
|
var color = colors[i].getColors(); |
|
return "rgb(" + color.r + "," + color.g + |
|
"," + color.b + ")"; |
|
} else { |
|
return ""; |
|
} |
|
}) |
|
.attr("d", path) |
|
.on("mousemove", function(d) { |
|
console.log(d); |
|
var html = ""; |
|
|
|
html += "<div class=\"tooltip_kv\">"; |
|
html += "<span class=\"tooltip_key\">"; |
|
html += id_name_map[d.id]; |
|
html += "</span>"; |
|
html += "<span class=\"tooltip_value\">"; |
|
html += (valueById.get(d.id) ? (Math.round(+valueById.get(d.id) / 10.) / 100) : ""); |
|
html += ""; |
|
html += "</span>"; |
|
html += "</div>"; |
|
|
|
$("#tooltip-container").html(html); |
|
$(this).attr("fill-opacity", "0.8"); |
|
$("#tooltip-container").show(); console.log(html); |
|
|
|
var coordinates = d3.mouse(this); |
|
|
|
var map_width = $('.categories-choropleth')[0].getBoundingClientRect().width; |
|
|
|
if (d3.event.layerX < map_width / 2) { |
|
d3.select("#tooltip-container") |
|
.style("top", (d3.event.layerY + 15) + "px") |
|
.style("left", (d3.event.layerX + 15) + "px"); |
|
} else { |
|
var tooltip_width = $("#tooltip-container").width(); |
|
d3.select("#tooltip-container") |
|
.style("top", (d3.event.layerY + 15) + "px") |
|
.style("left", (d3.event.layerX - tooltip_width - 30) + "px"); |
|
} |
|
}) |
|
.on("mouseout", function() { |
|
$(this).attr("fill-opacity", "1.0"); |
|
$("#tooltip-container").hide(); |
|
}); |
|
|
|
svg.append("path") |
|
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; })) |
|
.attr("class", "categories") |
|
.attr("transform", "scale(" + SCALE + ")") |
|
.attr("d", path); |
|
}; |
|
|
|
d3.json("https://s3-us-west-2.amazonaws.com/vida-public/geo/us.json", function(error, us) { |
|
makeMap(us); |
|
}); |
|
|
|
}); |
|
|
|
}; |
|
|
|
d3.csv("https://raw.githubusercontent.com/KirkHunter/KirkHunter.github.io/master/health.csv", draw); |
|
|
|
|
|
|
|
var freqData=[ |
|
{'State': 'AK', 'freq': {'high': 191.0, 'low': 5.0, 'mid': 31.0}}, |
|
{'State': 'AL', 'freq': {'high': 460.0, 'low': 3.0, 'mid': 22.0}}, |
|
|
|
|
|
|
|
|
|
{'State': 'CT', 'freq': {'high': 254.0, 'low': 4.0, 'mid': 23.0}}, |
|
|
|
{'State': 'DE', 'freq': {'high': 140.0, 'low': 9.0, 'mid': 20.0}}, |
|
|
|
{'State': 'GA', 'freq': {'high': 342.0, 'low': 4.0, 'mid': 22.0}}, |
|
|
|
|
|
|
|
|
|
{'State': 'IN', 'freq': {'high': 226.0, 'low': 5.0, 'mid': 21.0}}, |
|
|
|
|
|
{'State': 'LA', 'freq': {'high': 342.0, 'low': 3.0, 'mid': 24.0}}, |
|
|
|
{'State': 'MD', 'freq': {'high': 101.0, 'low': 3.0, 'mid': 10.0}}, |
|
|
|
|
|
|
|
{'State': 'MO', 'freq': {'high': 234.0, 'low': 4.0, 'mid': 23.0}}, |
|
{'State': 'MS', 'freq': {'high': 350.0, 'low': 3.0, 'mid': 22.0}}, |
|
|
|
|
|
{'State': 'ND', 'freq': {'high': 104.0, 'low': 5.0, 'mid': 15.0}}, |
|
{'State': 'NE', 'freq': {'high': 354.0, 'low': 7.0, 'mid': 23.0}}, |
|
|
|
|
|
|
|
{'State': 'NV', 'freq': {'high': 401.0, 'low': 11.0, 'mid': 43.0}}, |
|
|
|
|
|
|
|
|
|
{'State': 'PA', 'freq': {'high': 614.0, 'low': 4.0, 'mid': 27.0}}, |
|
|
|
|
|
{'State': 'SD', 'freq': {'high': 196.0, 'low': 6.0, 'mid': 21.0}}, |
|
|
|
|
|
|
|
|
|
{'State': 'VT', 'freq': {'high': 94.0, 'low': 7.0, 'mid': 15.0}}, |
|
|
|
{'State': 'WI', 'freq': {'high': 243.0, 'low': 5.0, 'mid': 19.0}} |
|
]; |
|
|
|
dashboard('#dashboard',freqData); |
|
</script> |
|
<body> |
|
<div id="tooltip-container"></div> |
|
<div id="canvas-svg"></div> |
|
</body> |