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.