Forked from William Savage's Pen P2 - Zoomable tree map - using Json & D3.js(v3).
A Pen by Jonas Almeida on CodePen.
<h1>Readmissions by Diagnosis Based on Primary Admission Diagnosis</h1> | |
<div id="body"> |
var treeData; | |
test1 = { | |
name:"Test1", children:[{ name: "Infection", children: [ { name: "Unspecified septicemia", icd9_code: "038.9", count: 19 }, { name: "Other postoperative infection", icd9_code: "998.59", count: 21 }, { name: "Urinary tract infection, site not specified", icd9_code: "599.0", count: 28 }, { name: "Intestinal infection due to Clostridium difficile", icd9_code: "008.45", count: 27 }, { name: "Obstructive chronic bronchitis with (acute) exacerbation", icd9_code: "491.21", count: 17 }]},{ name: "Heart Disease", children: [ { name: "Acute on chronic diastolic heart failure", icd9_code: "428.33", count: 12 }, { name: "Subendocardial infarction, initial episode of care", icd9_code: "410.71", count: 19 }, { name: "Acute on chronic systolic heart failure", icd9_code: "428.23", count: 29 }, { name: "Acute on chronic combined systolic and diastolic heart failure", icd9_code: "428.43", count: 28 }, { name: "Coronary atherosclerosis of native coronary artery", icd9_code: "414.01", count: 14 }, { name: "Atrial fibrillation", icd9_code: "427.31", count: 26 }]},{ name: "Kidney Disease", children: [ { name: "Acute kidney failure, unspecified", icd9_code: "584.9", count: 13 }]},{ name: "Neurological Disease", children: [ { name: "Cerebral embolism with cerebral infarction", icd9_code: "434.11", count: 13 }, { name: "Cerebral artery occlusion, unspecified with cerebral infarction", icd9_code: "434.91", count: 16 }, { name: "Subdural hemorrhage", icd9_code: "432.1", count: 23 }, { name: "Intracerebral hemorrhage", icd9_code: "431", count: 16 }, { name: "Unspecified transient cerebral ischemia", icd9_code: "435.9", count: 23 }, { name: "Cerebral aneurysm, nonruptured", icd9_code: "437.3", count: 13 }]},{ name: "Other", children: [ { name: "Other complications due to other vascular device, implant, and g", icd9_code: "996.74", count: 10 }, { name: "Other chest pain", icd9_code: "786.59", count: 26 }]}] | |
}; | |
test2 = { | |
name:"Test2", children:[{ name: "Infection", children: [ { name: "Obstructive chronic bronchitis with (acute) exacerbation", icd9_code: "491.21", count: 21 }, { name: "Pneumonia, organism unspecified", icd9_code: "486", count: 35 }, { name: "Unspecified septicemia", icd9_code: "038.9", count: 30 }, { name: "Pneumonitis due to inhalation of food or vomitus", icd9_code: "507.0", count: 36 }, { name: "Intestinal infection due to Clostridium difficile", icd9_code: "008.45", count: 32 }, { name: "Obstructive chronic bronchitis with acute bronchitis", icd9_code: "491.22", count: 27 }, { name: "Cellulitis and abscess of leg, except foot", icd9_code: "682.6", count: 42 }, { name: "Urinary tract infection, site not specified", icd9_code: "599.0", count: 29 }, { name: "Acute pancreatitis", icd9_code: "577.0", count: 27 }, { name: "Bronchitis, not specified as acute or chronic", icd9_code: "490", count: 30 }]},{ name: "Heart Disease", children: [ { name: "Acute on chronic diastolic heart failure", icd9_code: "428.33", count: 43 }, { name: "Acute on chronic systolic heart failure", icd9_code: "428.23", count: 29 }, { name: "Atrial fibrillation", icd9_code: "427.31", count: 27 }, { name: "Acute on chronic combined systolic and diastolic heart failure", icd9_code: "428.43", count: 24 }, { name: "Coronary atherosclerosis of native coronary artery", icd9_code: "414.01", count: 32 }, { name: "Subendocardial infarction, initial episode of care", icd9_code: "410.71", count: 20 }, { name: "Other pulmonary embolism and infarction", icd9_code: "415.19", count: 36 }]},{ name: "Kidney Disease", children: [ { name: "Acute kidney failure, unspecified", icd9_code: "584.9", count: 25 }]},{ name: "Other", children: [ { name: "Acute and chronic respiratory failure", icd9_code: "518.84", count: 28 }, { name: "Other chest pain", icd9_code: "786.59", count: 42 }]}] | |
}; | |
test3 = { | |
name:"Test3", children:[{ name: "Infection", children: [ { name: "Unspecified septicemia", icd9_code: "038.9", count: 30 }, { name: "Other postoperative infection", icd9_code: "998.59", count: 15 }, { name: "Pneumonia, organism unspecified", icd9_code: "486", count: 28 }, { name: "Urinary tract infection, site not specified", icd9_code: "599.0", count: 38 }, { name: "Intestinal infection due to Clostridium difficile", icd9_code: "008.45", count: 25 }, { name: "Obstructive chronic bronchitis with (acute) exacerbation", icd9_code: "491.21", count: 16 }, { name: "Cellulitis and abscess of leg, except foot", icd9_code: "682.6", count: 27 }, { name: "Pneumonitis due to inhalation of food or vomitus", icd9_code: "507.0", count: 22 }]},{ name: "Heart Disease", children: [ { name: "Coronary atherosclerosis of native coronary artery", icd9_code: "414.01", count: 32 }, { name: "Acute on chronic diastolic heart failure", icd9_code: "428.33", count: 35 }, { name: "Atrial fibrillation", icd9_code: "427.31", count: 16 }, { name: "Acute on chronic combined systolic and diastolic heart failure", icd9_code: "428.43", count: 21 }, { name: "Acute on chronic systolic heart failure", icd9_code: "428.23", count: 16 }, { name: "Subendocardial infarction, initial episode of care", icd9_code: "410.71", count: 35 }]},{ name: "Kidney Disease", children: [ { name: "Acute kidney failure, unspecified", icd9_code: "584.9", count: 28 }, { name: "Complications of transplanted kidney", icd9_code: "996.81", count: 40 }]},{ name: "Diabetes", children: [ { name: "Diabetes with other specified manifestations, type II or unspeci", icd9_code: "250.80", count: 23 }, { name: "Diabetes with neurological manifestations, type II or unspecifie", icd9_code: "250.60", count: 30 }]},{ name: "Other", children: [ { name: "Other chest pain", icd9_code: "786.59", count: 20 }, { name: "Dehydration", icd9_code: "276.51", count: 18 }]}]}; | |
treeData = test1; | |
var dataSets = ["TestData1", "TestData2", "TestData3"]; | |
var testData = [test1, test2, test3]; | |
var selectedDataset = dataSets[0]; | |
/* | |
To-do: | |
test multi-level | |
multiple datasets- transitions | |
*/ | |
// buttons | |
d3.select("#body").append("div") | |
.attr("class", "chart-label") | |
.text("Primary Admission Diagnosis: ") | |
d3.select("#body").append("div") | |
.attr("class", "selection-buttons-container") | |
.selectAll("div").data(dataSets) | |
.enter().append("div") | |
.text(function(d) { return d;}) | |
.attr("class", function(d) { | |
if (d == selectedDataset) | |
return "selection-button selected-button"; | |
else | |
return "selection-button"; | |
}) | |
.on("click", function(d) { | |
d3.select(".selected-button").classed("selected-button", false); | |
d3.select(this).classed("selected-button", true); | |
selectedDataset = d; | |
// update data | |
//treeData = test2; | |
updateTreeMap(testData[dataSets.indexOf(d)]); | |
zoom(node); | |
}); | |
var w = 1280 - 80, | |
h = 800 - 180, | |
x = d3.scale.linear().range([0, w]), | |
y = d3.scale.linear().range([0, h]), | |
color = d3.scale.category10(), | |
root, | |
node; | |
var treemap; | |
var svg = d3.select("#body").append("div") | |
.attr("class", "chart") | |
.style("width", w + "px") | |
.style("height", h + "px") | |
.append("svg:svg") | |
.attr("width", w) | |
.attr("height", h) | |
.append("svg:g") | |
.attr("id", "rootG") | |
.attr("transform", "translate(.5,.5)"); | |
initializeTreeMap(); | |
//d3.json("https://gist.githubusercontent.com/mbostock/1093025/raw/a05a94858375bd0ae023f6950a2b13fac5127637/flare.json", initializeTreeMap()); | |
function initializeTreeMap() { | |
node = root = treeData; | |
treemap = d3.layout.treemap() | |
.padding(2) | |
.round(false) | |
.size([w, h]) | |
.sticky(true) | |
.mode("squarify") | |
.value(function(d) { return d.count; }); | |
var data = treemap.nodes(root) | |
.filter(function(d) { return d.depth == 0 ? null : d; }) | |
.sort(function comparator(a, b) { return b.depth - a.depth;}); | |
var nodeGroups = svg.selectAll("g").data(data) | |
.enter().append("svg:g") | |
.attr("id", function(d) { return d.name.replace(/\s/g, "_");} ) | |
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }) | |
.attr("width", function(d) { return d.dx; }) | |
.attr("height", function(d) { return d.dy; }) | |
.on("click", function(d) { return zoom(d.children ? d : root); }); | |
nodeGroups | |
.append("title") | |
.text(function(d) | |
{ | |
if (d.icd9_code != null) | |
return d.name + " (" + d.icd9_code + ")\nNumber of Readmissions: " + d.value; | |
else | |
return d.name + "\nNumber of Readmissions: " + d.value; | |
}); | |
nodeGroups.filter(function(d) { return d.depth == 0 ? null : d; }) | |
.append("svg:rect") | |
.attr("class", "cell") | |
.attr("width", function(d) { return d.dx - 1; }) | |
.attr("height", function(d) { return d.dy - 1; }) | |
.style("fill-opacity", function(d) { return d.children ? .7 : .9; }) | |
.style("fill", function(d,i) | |
{ | |
if (d.depth == 1) { return color(d.name); } | |
else { return d3.rgb(color(d.parent.name)).brighter(i*0.1); } | |
}); | |
d3.select(window).on("click", function() { if (node != root) { zoom(root);} }); | |
zoom(root); | |
} | |
function updateTreeMap(newData) { | |
node = root = newData; | |
treemap = d3.layout.treemap() | |
.padding(2) | |
.round(false) | |
.size([w, h]) | |
.sticky(true) | |
.mode("squarify") | |
.value(function(d) { return d.count; }); | |
var data = treemap.nodes(newData) | |
.filter(function(d) { return d.depth == 0 ? null : d; }) | |
.sort(function comparator(a, b) { return b.depth - a.depth;}); | |
var dataJoin = svg.selectAll("g").data(data, | |
function(d) { return d.name }); | |
// remove exit | |
dataJoin.exit().selectAll("*").remove(); | |
dataJoin.exit().remove(); | |
// append enter | |
var enterGroups = dataJoin | |
.enter().append("svg:g") | |
.attr("id", function(d) { return d.name.replace(/\s/g, "_");} ) | |
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }) | |
.attr("width", function(d) { return d.dx; }) | |
.attr("height", function(d) { return d.dy; }) | |
.on("click", function(d) { return zoom(d.children ? d : root); }); | |
enterGroups.filter(function(d) { return d.depth == 0 ? null : d; }) | |
.append("svg:rect") | |
.attr("class", "cell") | |
.attr("width", function(d) { return 1; }) | |
.attr("height", function(d) { return 1; }) | |
.style("fill-opacity", function(d) { return d.children ? .7 : .9; }) | |
.style("fill", function(d,i) | |
{ | |
if (d.depth == 1) { return color(d.name); } | |
else { return d3.rgb(color(d.parent.name)).brighter(i*0.1); } | |
}); | |
// update | |
// reset cell color? | |
// title | |
svg.selectAll("title").remove(); | |
svg.selectAll("#rootG g") | |
.append("title") | |
.text(function(d) { | |
if (d.icd9_code != null) | |
return d.name + " (" + d.icd9_code + ")\nNumber of Readmissions: " + d.value; | |
else | |
return d.name + "\nNumber of Readmissions: " + d.value; | |
}); | |
// redraw in correct order | |
svg.selectAll("#rootG g").sort(function comparator(a, b) { | |
return b.depth - a.depth; | |
}); | |
zoom(root); | |
} | |
function accumulate(d) { | |
return d.children | |
? d.count = d.children.reduce(function(d) { return accumulate(d); }, 0) | |
: d.count; | |
} | |
function size(d) { | |
return d.size; | |
} | |
function count(d) { | |
return 1; | |
} | |
function zoom(d) { | |
// make [zoomed] root (+ancestors) container transparent | |
svg.selectAll("g").selectAll("rect") | |
.attr("display", function(d2) { return d2.depth <= d.depth ? "none" : "inline"; }); | |
var kx = w / d.dx, ky = h / d.dy; | |
x.domain([d.x, d.x + d.dx]); | |
y.domain([d.y, d.y + d.dy]); | |
// destroy old text, create new | |
svg.selectAll("text").remove(); | |
var newText = svg.selectAll("g") | |
.filter(function(d2) | |
{ return (d2.depth == d.depth + 1) && (d2.parent == d) ? d2 : null; }) | |
.append("svg:text") | |
.attr("x", function(d) { return kx * d.dx / 2; }) | |
.attr("y", function(d) { return ky * d.dy / 2; }) | |
//.attr("dy", ".35em") | |
.attr("text-anchor", "middle") | |
.style("font-size", 30) | |
.style("opacity", 0) | |
.style("pointer-events", "none") | |
.text(function(d) { return d.name; }); | |
newText.transition().duration(1000) | |
.ease("cubic") | |
.style("opacity", 1); | |
// rotate text where appropriate | |
svg.selectAll("g") | |
.filter(function(d) { return ((ky * d.dy) > 1.3*(kx * d.dx)) ? d : null; }) | |
.selectAll("text") | |
.attr("transform", function(d) { return "rotate(90 " + kx * d.dx / 2 + " " + ky * d.dy / 2 + ")"; }); | |
// add line breaks and scale appropriately | |
var selectGroup = svg.selectAll("g") | |
.filter(function(d2) { return (d2.depth == d.depth + 1) && (d2.parent == d) ? d2 : null; }); | |
selectGroup.call(fitText, kx, ky); | |
// add count | |
selectGroup.selectAll("text").each(function(d) | |
{ | |
var x = d3.select(this).attr("x"); | |
d3.select(this) | |
.append("tspan") | |
.text(function(d) { return d.value; }) | |
.attr("x", x) | |
.attr("dy", "1.1em"); | |
}); | |
// apply transform to g | |
var t = svg.selectAll("g").transition().duration(1000) | |
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; }); | |
t.select("rect") | |
.attr("width", function(d) { return kx * d.dx - 1; }) | |
.attr("height", function(d) { return ky * d.dy - 1; }); | |
node = d; | |
d3.event.stopPropagation(); | |
} | |
function fitText(d, kx, ky) { | |
d.each( function() | |
{ | |
// check rect bounds vs text | |
var g = d3.select(this); | |
var text = g.select("text"); | |
var rect = g.select("rect"); | |
var textBBox = text[0][0].getBBox(); | |
var rectWidthLimit = rect.datum().dx * kx; | |
var rectHeightLimit = rect.datum().dy * ky; | |
if (text.attr("transform") != null) | |
{ | |
rectWidthLimit = rect.datum().dy * ky; | |
rectHeightLimit = rect.datum().dx * kx; | |
} | |
// check for overlap, if so, wrap | |
if (textBBox.width > rectWidthLimit - 50) | |
{ | |
var lineHeight = 1.2; // ems | |
var x = text.attr("x"); | |
var y = text.attr("y"); | |
var dy = parseFloat(text.attr("dy")); | |
var width = Math.round(text.text().length/2); | |
// find first space after mid-point of text | |
var breakIndex = width + text.text().substring(width, text.text().length).indexOf(" "); | |
if (text.text().substring(width, text.text().length).indexOf(" ") == -1) | |
{ | |
breakIndex = text.text().substring(width, text.text().length).indexOf(" "); | |
} | |
// then split text in two at found space | |
var wrapText = text.text().substring(breakIndex + 1, text.text().length); | |
if (breakIndex > 0) | |
{ | |
var nextText = text.text().substring(0, breakIndex + 1); | |
text.text(nextText).attr("dy", "-0.5em") | |
text.append("tspan").text(wrapText).attr("x", text.attr("x")).attr("dy", "1.1em"); | |
} | |
// if text is still overlaps, decrease textSize | |
textBBox = text[0][0].getBBox(); | |
while ((textBBox.width > rectWidthLimit - 50) || ((textBBox.height > rectHeightLimit - 30))) | |
{ | |
/* | |
console.log(text.text()); | |
console.log("text width: " + Math.round(textBBox.width)); | |
console.log("rect limit: " + Math.round(rectLimit)); | |
console.log("rect x: " + rect.attr("width")); | |
console.log("rect kx: " + kx); | |
console.log("text size: " + text.style("font-size")); | |
*/ | |
text.style("font-size", parseInt(text.style("font-size"))-2); | |
textBBox = text[0][0].getBBox(); | |
} | |
} | |
}); | |
} |
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> | |
<script src="http://d3js.org/d3.v3.min.js"></script> |
.chart { | |
display: block; | |
margin: auto; | |
margin-top: 40px; | |
} | |
rect { | |
fill: clear; | |
rx: 5; | |
ry: 5; | |
cursor: pointer; | |
} | |
.chart-label { | |
font-size: 20px; | |
margin-left: 10px; | |
margin-bottom: 10px; | |
} | |
.selection-buttons-container { | |
width: 500px; | |
margin-bottom: 30px; | |
} | |
.selection-button { | |
float: left; | |
font-size: 20px; | |
margin-left: 10px; | |
cursor: pointer; | |
color: gray; | |
} | |
.selected-button { | |
font-size: 20px; | |
font-weight: bold; | |
color: blue; | |
} | |
/* pointer-events: none; */ |
Forked from William Savage's Pen P2 - Zoomable tree map - using Json & D3.js(v3).
A Pen by Jonas Almeida on CodePen.