|
<!doctype html> |
|
<!-- Based on: http://flowingdata.com/2014/07/02/jobs-charted-by-state-and-salary/ |
|
license: gpl-3.0 |
|
--> |
|
|
|
<html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<title>Interactive Treemap with Shading for Severity</title> |
|
|
|
<script src="http://d3js.org/d3.v3.min.js"></script> |
|
|
|
<style> |
|
/* Normalize */ |
|
body, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, fieldset, input, p, blockquote, th, td, legend { margin: 0; padding: 0;} /*div,*/ |
|
|
|
h1,h2,h3,h4,h5,h6 { font-family: Helvetica, Arial, sans-serif; font-weight: bold; } |
|
address, caption, cite, code, dfn, th, var { font-style: normal; font-weight: normal; } |
|
table { border-collapse: collapse; border-spacing: 0; } |
|
fieldset,img { border: 0; } |
|
caption,th { text-align: left; } |
|
/* @end Normalize */ |
|
|
|
/* General */ |
|
body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; margin: auto; margin-left:1em; position: relative; width: 954px; } |
|
p, ul { margin-top:1em; } |
|
/*form { font-size: 12px; }*/ |
|
.clr { clear: both; } |
|
/* @end General */ |
|
|
|
.container { margin-left:100px; } |
|
|
|
/* Filters */ |
|
#filters { height: 4em; margin-top:2em; } |
|
#filters label { font-size: 90%; /*font-weight: bold; */ } |
|
#filters #leftfilters { float: left; width: 130px; border-right: 1px solid #e0e0e0; height: 80px; margin-right: 10px; } |
|
#filters #categorySelect { margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #e0e0e0; } |
|
#filters #rightfilters { width: 780px; float: left; } |
|
/* @end Filters */ |
|
|
|
/* Progress loader */ |
|
#loadprogress { position: relative; top: -350px; left: 470px; width: 100px; text-align: center; font-size: 85%; text-transform: uppercase; line-height: 1.4em; } |
|
/* @end Progress loader */ |
|
|
|
/* Treemap */ |
|
.node { border: solid 1px #999; /* org was .3px */ font: 10px Helvetica, sans-serif; text-align: left; vertical-align: middle; line-height: 1.2em; overflow: hidden; position: absolute; color: #333333; cursor: default; background: #fff; } |
|
.parent.node { border: 1px solid #999; background: #999; } |
|
.node .childlabel { padding: 2px 0px 0 4px; color: gray; /* ligher would be c3c3c3; gray:808080 */ } |
|
.parentlabel { position: absolute; text-transform: uppercase; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: bold; padding: 10px; overflow: hidden; color: #9a16bf; pointer-events: none; display: none; } |
|
#occundefined { background: #999; } |
|
|
|
/* child boxes that become shaded. If gradient is added, go dark and make this text white */ |
|
.node.highlight { |
|
background: #f2dede; /* shade of blocks. Green is d1f77b, red, bootstrap warning: f2dede; pink:ffc0cb, LightCoral:F08080 */ |
|
color: #000; /* Options for text darkness: black, gray, c3c3c3 */ |
|
font-weight: bold; |
|
} |
|
.node.highlight:hover, .node:hover { background: #ff6; } |
|
.node.selected { background: #ff6; color: #000; } |
|
.parent.node:hover { background: #999; } |
|
/* @end Treemap */ |
|
|
|
/* Slider */ |
|
.axis { font-size: 85%; -webkit-user-select: none; -moz-user-select: none; user-select: none; } |
|
.axis .domain { fill: none; stroke: #333; stroke-opacity: .4; stroke-width: 7.5px; stroke-linecap: square; } |
|
.slider .highlighter { |
|
fill: #999999; /* OLD: d1f77b. change this if you change the box shade */ |
|
stroke: #999999; |
|
/* fill: black;*/ |
|
/* stroke: black;*/ |
|
} |
|
.axis .halo { fill: none; stroke: #fff; stroke-width: 6px; stroke-linecap: square; } |
|
.slider .handle { fill: #fff; stroke: #000; stroke-opacity: .5; stroke-width: 1.25px; pointer-events: none; } |
|
/* @end Slider */ |
|
|
|
/* Tooltip */ |
|
#tooltip { position:absolute; width: 200px; line-height: 1.1em; height: auto; padding: 8px; background-color: #fff; pointer-events: none; font-size: 12px; -webkit-box-shadow: 4px 4px 10px rgba(0,0,0,0.4); -moz-box-shadow: 4px 4px 10px rgba(0,0,0,0.4); box-shadow: 4px 4px 10px rgba(0,0,0,0.4); border: 1px solid #ddd; } |
|
#tooltip.hidden { display: none; } |
|
#tooltip #contGroup { font-size: 9px; font-weight: normal; margin-bottom: 4px; text-transform: uppercase; } |
|
#tooltip #groupTitle { font-weight: bold; margin-bottom: 8px; border-bottom: 1px solid #000; padding-bottom: 7px; } |
|
#tooltip #pagesPoints { text-align: center; } |
|
#tooltip #groupTraffic { margin-bottom: 5px; text-align: center; } |
|
/* @end Tooltip */ |
|
|
|
.facetReportButton { padding:6px 6px 8px 6px; margin: 0 4px 0 4px; } |
|
.facetReportButton span { margin-left:.3em; } |
|
#credits { font-size: 10px; text-align: right; } |
|
ul, li { margin-left:1em; } |
|
</style> |
|
</head> |
|
|
|
<body> |
|
|
|
<h2>Visualize and Accelerate Web Site Repairs</h2> |
|
|
|
<p>Rectangle volume shows relative number of HTML pages; some communication packages are small; some are large. |
|
Gray shading plus zero points means no errors were found. A shade of blue plus total count of points |
|
shows severity of the problem being analyzed. White indicates that the microsite was not accessed during the |
|
period for which traffic data was taken.</p> |
|
|
|
<div id="filters"> |
|
|
|
<div id="leftfilters"> |
|
|
|
<div id="showRepairCats"> |
|
<form> |
|
<input type="checkbox" name="parentcheck" value="on" id="parentcheck"/> |
|
<label for="parentcheck"><strong>Show topics</strong></label> |
|
</form> |
|
</div><!-- end showRepairCats --> |
|
</div><!-- end leftfilters --> |
|
|
|
|
|
<div id="rightfilters"> |
|
<div id="sliderholder"> |
|
<label>Drag the traffic slider (below) all the way left; this is your <strong><em>total severity</em></strong> for all problems everywhere, for any amount of traffic. Then drag all the way right; dragging back left, you will see the highest-traffic properties appear, red = problems; green = OK. Score in points appears after each title. Default = 5,000 page views/mo.</label> |
|
</div> |
|
</div><!-- end rightfilters --> |
|
|
|
|
|
<div class="clr"></div> |
|
</div><!-- end filters --> |
|
|
|
<div id="groups"> |
|
<div id="treemapholder"></div> |
|
<div id="tooltip" class="hidden"> |
|
<div id="contGroup"></div> |
|
<div id="groupTitle"></div> |
|
<div id="groupTraffic"></div> |
|
<div id="pagesPoints"></div> |
|
</div> |
|
<div id="loadprogress">Loading Data…<br /><span class="progvalue">0</span>%</div> |
|
</div><!-- end #groups --> |
|
|
|
|
|
<script> |
|
var margin = {top: 40, right: 0, bottom: 10, left: 0}, |
|
width = 900, |
|
height = 475; |
|
|
|
var boxVolumeDimension = 'groupPageCount'; |
|
var boxShadingDimension = 'repairTotCount' |
|
var medCutoff = 5000; |
|
var showParentLab = false; |
|
|
|
var treemap = d3.layout.treemap() |
|
.size([width, height]) |
|
.sticky(true) |
|
.padding(2) |
|
.sort(function(a, b) { return a.value - b.value; }) |
|
.value(function(d) { return d[boxVolumeDimension]; }); |
|
|
|
var div = d3.select("#treemapholder").append("div") |
|
.attr("id", "treemap") |
|
.style("position", "relative") |
|
.style("width", (width + margin.left + margin.right) + "px") |
|
.style("height", (height + margin.top + margin.bottom) + "px") |
|
.style("left", margin.left + "px") |
|
.style("top", margin.top + "px"); |
|
|
|
var colorScale |
|
|
|
// --- Load data --------------------------------------------------------------- |
|
d3.json("exampleData.json") |
|
|
|
// Show loading progress for data file |
|
.on("progress", function() { |
|
d3.select("#loadprogress .progvalue").text(Math.round(100 * d3.event.loaded / 1075072)); // count was based on size of HUGE orig json file |
|
}) |
|
.get(function(error, root) { |
|
var maxshade = 0; |
|
for (var i = 0; i < root.children.length; i++) { |
|
var innerChildren = root.children[i]; |
|
for (var j = 0; j < innerChildren.children.length; j++) { |
|
var curRepairTotCount = innerChildren.children[j].repairTotCount; |
|
if (curRepairTotCount > maxshade) maxshade = curRepairTotCount; |
|
} |
|
} |
|
|
|
colorScale = d3.scale.linear() |
|
.range(['#9ECAE1', '#08306B']) // pink to maroon, #FFA07A', '#800000'. dark red: a94442. See https://en.wikipedia.org/wiki/Web_colors |
|
.domain([1, maxshade]); |
|
|
|
d3.select("#loadprogress").remove(); |
|
|
|
// node: Load rectangles, arrange, highlight |
|
var node = div.datum(root).selectAll(".node") |
|
.data(treemap.nodes) |
|
.enter().append("div") |
|
.attr("class", function(d) { return d.children ? "parent node" : "node"; }) |
|
.attr("id", function(d) { return 'pagegroupnode' + d.pageGroupID }) |
|
.call(position); |
|
|
|
// childlabel: microsite name and total points. |
|
var label = node.append("div") |
|
.attr("class", "childlabel") |
|
.text(function(d) { |
|
if (d.children || d.dx*d.dy < 800) { return null; } |
|
else { return d.name + ' (' + numberWithCommas(d.repairTotCount) + ')' } }); |
|
|
|
|
|
// .parentlabel: Append labels to parent rectangles upon request - "Show topics" |
|
d3.selectAll(".parent").data().map(function(d) { |
|
if (d.name === "contentByGroup") return null; |
|
if (d.dx <= 22) return null; |
|
if (d.dy <= 22) return null; |
|
div.append("div") |
|
.text(d.name) |
|
.style("left", d.x + "px") |
|
.style("top", d.y + "px") |
|
.style("width", (d.dx-22) + "px") |
|
.style("height", (d.dy-22) + "px") |
|
.attr("class", "parentlabel") |
|
.attr("id", "plab"+d.pageGroupID); |
|
}); |
|
|
|
//Color, shading of boxes |
|
node |
|
.attr("class", medHighlight) |
|
.style("background", getColor); |
|
//.style("fill", function(d) { return colorScale(d.value)}); |
|
|
|
// Repair category selection |
|
var select = d3.selectAll("select").on("change", function change() { |
|
|
|
// Update nodes to new state |
|
boxVolumeDimension = this.value; |
|
var value = function(d) { return d[boxVolumeDimension]; } |
|
node |
|
.data(treemap.value(value).nodes) |
|
.transition() |
|
.duration(1000) |
|
.call(position) |
|
.call(positionParentLabels); |
|
|
|
// Adjust content group labels |
|
node.selectAll("div.childlabel") |
|
.text(function(d) { |
|
if (d.children || d.dx*d.dy < 800) { return null; } |
|
else { return d.name; } |
|
}); |
|
|
|
// Adjust box-traffic highlighting |
|
node |
|
.attr("class", medHighlight); |
|
}); |
|
|
|
// Adjust parent labels on checkbox |
|
d3.selectAll("#parentcheck").on("change", function change() { |
|
showParentLab = this.checked; |
|
d3.selectAll(".parentlabel") |
|
.call(positionParentLabels); |
|
}); |
|
|
|
// Retrieve CSV for a content group |
|
node.on("click", function(d){ |
|
if(!d.children){ |
|
window.open(d.url, '_top'); // window.open(d.url) for new tab; _top or _parent, page will open in same window |
|
}}); |
|
|
|
// Tooltip ----------------------------------------------------------------------------------- |
|
node.on("mouseover", function(d) { |
|
if (!d.children) { |
|
|
|
// Content group name |
|
d3.select("#tooltip #contGroup").text(d.parent.name); |
|
d3.select("#tooltip #groupTitle").text(d.name); |
|
|
|
// Traffic count |
|
if (d.aveMonthlyViewsGr == -99) { |
|
var trafficText = "Estimate unavailable"; |
|
} else if (d.aveMonthlyViewsGr > 180000) { |
|
var trafficText = "More than 180,000 per month" |
|
} else { |
|
var trafficText = numberWithCommas(d.aveMonthlyViewsGr) + " Ave views/month" |
|
} |
|
d3.select("#tooltip #groupTraffic").text(trafficText); |
|
|
|
// Page count and point total |
|
var numericResultText = numberWithCommas(d[boxVolumeDimension]) + " pages" + " / " + numberWithCommas(d.repairTotCount) + " points"; |
|
d3.select("#tooltip #pagesPoints").text(numericResultText); |
|
|
|
// Adjust the width and height |
|
var w = d3.select("#tooltip").style("width"); |
|
var h = d3.select("#tooltip").attr("height"); |
|
|
|
// Get mouse position and then adjust tooltip position accordingly |
|
var groupsNode = d3.select("#groups").node(); |
|
var absMousePos = d3.mouse(groupsNode); |
|
|
|
if (absMousePos[0] > 700) { |
|
var xpos = absMousePos[0] - parseFloat(w) - 30; |
|
} else { |
|
var xpos = absMousePos[0] + 10; |
|
} |
|
|
|
if (absMousePos[1] > 400) { |
|
var ypos = absMousePos[1] - 50; |
|
} else { |
|
var ypos = absMousePos[1] - 20; |
|
} |
|
|
|
d3.select("#tooltip") |
|
.style("left", xpos + "px") |
|
.style("top", ypos + "px"); |
|
|
|
// Display the tooltip |
|
d3.select("#tooltip").classed("hidden", false); |
|
} |
|
}) |
|
.on("mouseout", function(d) { |
|
// Hide the tooltip |
|
d3.select("#tooltip").classed("hidden", true); |
|
}); |
|
|
|
|
|
|
|
// Slider ----------------------------------------------------------------------------------- |
|
var slideMargin = {top: 0, right: 30, bottom: 10, left: 8}, |
|
slideWidth = 760 - slideMargin.left - slideMargin.right, // was 773 |
|
slideHeight = 45 - slideMargin.bottom - slideMargin.top; // was 75 |
|
|
|
var x = d3.scale.linear() |
|
.domain([1, 100000]) |
|
.range([0, slideWidth]) |
|
.clamp(true); |
|
|
|
var brush = d3.svg.brush() |
|
.x(x) |
|
.extent([0,0]) |
|
.on("brush", brushed); |
|
|
|
var svg = d3.select("#sliderholder").append("svg") |
|
.attr("width", slideWidth + slideMargin.left + slideMargin.right) |
|
.attr("height", slideHeight + slideMargin.top + slideMargin.bottom) |
|
.append("g") |
|
.attr("transform", "translate(" + slideMargin.left + "," + slideMargin.top + ")"); |
|
|
|
svg.append("g") |
|
.attr("class", "x axis") |
|
.attr("transform", "translate(0," + slideHeight / 2 + ")") |
|
.call(d3.svg.axis() |
|
.scale(x) |
|
.orient("bottom") |
|
.tickFormat(function(d) { |
|
if (d == 100000) return '>' + numberWithCommas(d); |
|
else return numberWithCommas(d); |
|
}) |
|
.tickSize(0) |
|
.tickPadding(12)) |
|
.select(".domain") |
|
.select(function() { return this.parentNode.appendChild(this.cloneNode(true)); }) |
|
.attr("class", "halo"); |
|
|
|
|
|
var slider = svg.append("g") |
|
.attr("class", "slider") |
|
.call(brush); |
|
|
|
slider.selectAll(".extent,.resize") |
|
.remove(); |
|
|
|
slider.select(".background") |
|
.attr("height", slideHeight); |
|
|
|
var highlighter = slider.append("rect") |
|
.attr("class", "highlighter") |
|
.attr("height", 4) |
|
.attr("x", x(medCutoff)) |
|
.attr("width", slideWidth-x(medCutoff)+2) |
|
.attr("transform", "translate(0," + (slideHeight / 2 - 2) + ")"); |
|
|
|
var handle = slider.append("circle") |
|
.attr("class", "handle") |
|
.attr("transform", "translate(0," + slideHeight / 2 + ")") |
|
.attr("r", 7) |
|
.attr("cx", x(medCutoff)); |
|
|
|
// Functions ----------------------------------------------------------------------------------- |
|
// Adjust node colors accordingly when traffic slider is moved |
|
function brushed() { |
|
var value = brush.extent()[0]; |
|
|
|
if (d3.event.sourceEvent) { // not a programmatic event |
|
value = x.invert(d3.mouse(this)[0]); |
|
brush.extent([value, value]); |
|
} |
|
handle.attr("cx", x(value)); |
|
highlighter |
|
.attr("x", x(value)) |
|
.attr("width", slideWidth-x(value)+2); |
|
var highlight = function(d) { |
|
var cssclass; |
|
if (d.children) { |
|
cssclass = "parent node"; |
|
} else if (d.aveMonthlyViewsGr > value && !d.children) { |
|
cssclass = "node highlight"; |
|
} else { |
|
cssclass = "node"; |
|
} |
|
return cssclass; |
|
} |
|
node |
|
.attr("class", highlight) |
|
.style("background", getColor); |
|
|
|
// Set the current traffic |
|
medCutoff = value; |
|
} |
|
}); // end on load event |
|
|
|
function position() { |
|
this.style("left", function(d) { return d.x + "px"; }) |
|
.style("top", function(d) { return d.y + "px"; }) |
|
.style("width", function(d) { return Math.max(0, d.dx - 1) + "px"; }) |
|
.style("height", function(d) { return Math.max(0, d.dy - 1) + "px"; }); |
|
} |
|
|
|
function positionParentLabels() { |
|
d3.selectAll(".parent").data().map(function(d) { |
|
|
|
d3.select("#plab" + d.pageGroupID) |
|
.transition().duration(1000) |
|
.style("left", d.x + "px") |
|
.style("top", d.y + "px") |
|
.style("width", (d.dx-22) + "px") |
|
.style("height", (d.dy-22) + "px") |
|
.style("display", function() { return d.dx > 22 && showParentLab ? "block" : "none" }); |
|
}); |
|
} |
|
|
|
function medHighlight(d) { |
|
if (d.children) { |
|
return "parent node"; |
|
} else if (d.aveMonthlyViewsGr > medCutoff && !d.children) { |
|
return "node highlight"; |
|
} else { |
|
return "node"; |
|
} |
|
} |
|
|
|
// Color range |
|
function getColor(d) { |
|
if (d.children) { |
|
return null; |
|
} else if (d.aveMonthlyViewsGr > medCutoff && !d.children) { |
|
if (d.repairTotCount < 1) return "#D3D3D3"; // old d1f77b |
|
return colorScale(d.repairTotCount); |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
function numberWithCommas(x) { |
|
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
|
} |
|
</script> |
|
|
|
|
|
|
|
</body> |
|
</html> |