xxxxxxxxxx
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"> "></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<style type="text/css">
.chart-area {
font-family: Myriad Pro;
font-size: 11px;
font-weight: lighter;
color: #535353;
}
#menu {
margin-left: 20px;
margin-top: 5px;
font-weight: bold;
}
select {
font-weight: lighter;
border: 0 !important;
}
p {
margin-bottom: 0px;
margin-top: 5px;
}
.hoverword {
font-weight: bold;
cursor: default;
}
.axis path, line {
fill: none;
stroke: #535353;
stroke-rendering: crispEdges;
}
.axis text {
opacity: 0.8;
}
g.tick text {
font-size: 8px;
}
.x.axis.threshold {
stroke-dasharray: 8,8;
opacity: 0.8;
}
.sm.x.axis path,
.sm.x.axis line {
stroke-width : 0.5px;
}
.sm.y.axis {
display: none;
}
.main.axis path,
.main.axis line {
stroke-width: 0.8px;
}
.main-axis-label {
font-size: 9px;
font-weight: lighter;
opacity: 0.8;
}
.selected-month-text {
font-size: 20px;
font-weight: bold;
opacity: 0.6;
}
.sm-axis-label {
font-size: 9px;
opacity: 0.8;
cursor: pointer;
color: red; /* doesn't work */
}
.label-selected {
font-size: 11px;
font-weight: bold;
}
.school-hours {
opacity: 0.05;
fill: #4D4D4D;
}
.room-container {
margin-top: 10px;
height: 260px;
column-fill: auto;
column-count: 3;
-webkit-column-count: 3;
-moz-column-count: 3;
}
.room {
cursor: pointer;
width: 120px;
padding-left: 5px;
font-weight: lighter;
font-size: 10px;
line-height: 15px;
opacity: 0.3;
}
.selectable {
opacity: 1;
}
.room-selected {
color: #D93333;
}
.voronoi path {
fill: none;
pointer-events: all;
}
.line {
fill: none;
stroke: #10638B;
stroke-width: 1px;
}
.sm.line {
opacity: 0.3;
}
.main.line {
opacity: 0.9;
}
.line-selected {
stroke: #D93333;
}
.sm.line-selected {
stroke-width: 1.5px;
opacity: 0.7;
}
.main.line-selected {
stroke-width: 3px;
}
.sm.line-hover {
stroke-width: 1.5px;
}
.main.line-hover {
stroke-width: 3px;
}
.focus text {
font-weight: normal;
font-size: 10px;
opacity: 0.8;
}
.focus-line {
stroke-dasharray: 2,2;
opacity: 0.8;
}
.focus-label {
text-shadow: 0 1px 0 #FFF, 1px 0 0 #FFF, 0 -1px 0 #FFF, -1px 0 0 #FFF;
}
</style>
<div class="chart-area">
<p id="menu">Select school: <select></select></p>
<div class="row-fluid">
<div class="col-md-12" id="monthcontainer"> </div>
</div>
<div class="row-fluid">
<div class="col-md-2">
<div class="row-fluid" id="roomcontainer">
<p>
<b>
Each line in this graphic represents the average daily temperature for an single (fake) month-classroom.
</b>
</br>
</br>
Choose a school from the dropdown and click on a month to toggle the main viewport.
Click on the classrooms listed below to compare or rollover the main viewport to highlight.
</br>
</p>
</div>
</div>
<div class="col-md-10" id="mainchart"></div>
</div>
</div>
</head>
<body>
<script type="text/javascript">
var parser = d3.time.format("%H:%M");
/* margins */
var margin = {top: 10, right: 10, bottom: 20, left: 10};
var smw = 90 - margin.left - margin.right;
var smh = 130 - margin.top - margin.bottom;
var w = 820 - margin.left - margin.right;
var h = 625 - margin.top - margin.bottom;
var menu = d3.select("#menu select") // school menu
.on("change", changeschool);
var altKey;
d3.select(window)
.on("keydown", function() { altKey = d3.event.altKey; })
.on("keyup", function() { altKey = false; });
/* path functions */
var smlinefunction = d3.svg.line()
.x(function(d) { return smxScale(d.Time); })
.y(function(d) { return smyScale(d.Temp_F); })
.interpolate("cardinal");
var linefunction = d3.svg.line()
.x(function(d) { return xScale(d.Time); })
.y(function(d) { return yScale(d.Temp_F); })
.interpolate("cardinal");
var voronoi = d3.geom.voronoi()
.x(function(d) { return xScale(d.Time); })
.y(function(d) { return yScale(d.Temp_F); })
.clipExtent([[0,0], [w,h]]);
/* create scales */
var smxScale = d3.time.scale()
.domain([parser.parse("00:00"), parser.parse("24:00")])
.range([0,smw]);
var smyScale = d3.scale.linear()
.range([smh,0]);
var xScale = d3.time.scale()
.domain([parser.parse("00:00"), parser.parse("24:00")])
.range([0,w]);
var yScale = d3.scale.linear()
.range([h,0]);
/* create axes */
var smxAxis = d3.svg.axis()
.scale(smxScale)
.orient("bottom")
.tickValues([parser.parse("12:00")])
.tickSize(0)
.tickFormat(d3.time.format("%H:%M"));
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(24)
.tickSize(0)
.tickFormat(d3.time.format("%H:%M"));
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(8)
.tickSize(0)
.tickFormat(function(a) {return a + "°"; });
// key function
var key = function(d,i) {
return school.replace(/\W+/g, '') + month + d.key.replace(/\W+/g, '')+ i; //can either get it to transition nicely or have the room clicks work...right now click has priority but I would like both...
}
/* layout */
var monthcontainer = d3.select("#monthcontainer")
.append("div")
.attr("class", "month-container");
var roomcontainer = d3.select("#roomcontainer")
.append("div")
.attr("class", "room-container");
var mainchart = d3.select("#mainchart")
.append("div")
.attr("class", "main-chart");
/* create svgs */
var allMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var allmonthsvgs = monthcontainer.selectAll("svg") // create SVG element for each month
.data(allMonths)
.enter().append("svg")
.attr("class", function(d) { return d;})
.attr("width", smw + margin.left + margin.right)
.attr("height", smh + margin.bottom + margin.top)
.append("g")
.attr("class", function(d) { return ("chart " + d); })
.attr("id", function(d) { return d; })
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var chart = mainchart.append("svg")
.attr("width", w + 5*margin.left + 6*margin.right) // leave room for focus labels
.attr("height", h + margin.bottom + margin.top)
.append("g")
.attr("class", "chart")
.attr("transform", "translate(" + 2*margin.left + "," + margin.top + ")")
.attr("width", w)
.attr("height", h);
/*------ DATA CALLBACK ---------*/
var rawdata, school, month;
d3.csv("tdata.csv", function(error, data) {
if (error) return console.log(error);
rawdata = data;
rawdata.forEach (function(d) {
d.Time = parser.parse(d.Time);
d.Temp_F = +d.Temp_F;
d.Key = +d.Key;
});
smminTemp = d3.min(rawdata, function(d) { return d.Temp_F; }); // month chart y-domain defined by min,max across all schools
smmaxTemp = d3.max(rawdata, function(d) { return d.Temp_F; });
smyScale.domain([smminTemp-5, smmaxTemp]); // leave room for month label
menu.selectAll("option") // load schools into menu dropdown for selection
.data(d3.map(rawdata, function(d) { return d.School; }).keys())
.enter().append("option")
.text(function(d) { return d; });
init();
changeschool();
});
/*------ DRAW ---------*/
var nesteddata, nesteddataforvoronoi;
function update() {
//FILTER DATA
data = rawdata.filter(function(d) { return (d.School == school & d.Month == month); });
nesteddata = d3.nest()
.key(function(d) { return d.Room; })
.entries(data);
nesteddataforvoronoi = d3.nest()
.key(function(d) { return xScale(d.Time) + "," + yScale(d.Temp_F); })
.rollup(function(v) { return v[0]; })
.entries(d3.merge(nesteddata.map(function(d) { return d.values; })))
.map(function(d) { return d.values; });
//RESET
voronoiGroup.selectAll("path").remove(); //reset voronoi
roomcontainer.selectAll("div")
.classed("selectable", false);
//JOIN
var lines = linesvg.selectAll("path")
.data(nesteddata, key);
var voronois = voronoiGroup.selectAll("path")
.data(voronoi(nesteddataforvoronoi));
monthRooms = d3.map(data, function(d) { return d.Room; }).keys(); // not working
roomcontainer.selectAll("div")
.each(function(d) {
var room = d3.select(this);
monthRooms.forEach(function(key) {
if (d == key) {
room.classed("selectable", true);
}
});
});
//ENTER
lines.enter()
.append("path")
.attr("class", function(d) { return "main line " + month + " " + d.key.replace(/\W+/g, '') + " " + school.replace(/\W+/g, ''); })
.attr("d", function(d) { return linefunction(d.values); })
focus.append("line")
.attr("class", "focus-line");
focus.append("text")
.attr("class", "focus-label")
.attr("x", 5)
.attr("y", -5);
focus.append("text")
.attr("class", "focus-time")
.attr("x", -12);
voronois.enter()
.append("path")
.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.datum(function(d) { return d.point; })
.on("mouseover", mouseover)
.on("mouseout", mouseout);
//UPDATE
d3.select(".main.x.axis.threshold")
.transition()
.duration(750)
.attr("transform", "translate(0," + yScale(85) + ")")
.call(xAxis.tickFormat("").tickSize(0));
d3.select(".y.axis")
.transition()
.duration(750)
.call(yAxis);
d3.select(".selected-month-text")
.transition()
.duration(750)
.text(month);
lines.transition()
.duration(750)
.attr("class", function(d) {
var curr = "main line " + month + " " + d.key.replace(/\W+/g, '') + " " + school.replace(/\W+/g, '');
var button = d3.select(".room." + d.key.replace(/\W+/g, '') + "." + school.replace(/\W+/g, ''));
if (button.classed("room-selected")) {
var curr = curr + " line-selected";
}
return curr;
})
.attr("d", function(d) {return linefunction(d.values); });
//EXIT
lines.exit().remove();
}
/*------ HELPER FUNCTIONS ---------*/
function changeschool() {
school = menu.property("value");
data = rawdata.filter(function(d) { return (d.School == school); });
month = d3.map(data, function(d) {return d.Month; }).keys()[0];
//RESET
d3.selectAll(".sm-axis-label").remove(); //remove month labels
d3.selectAll(".sm.line").remove(); //remove month paths
d3.selectAll(".room").remove(); //remove rooms
d3.selectAll("path")
.classed("line-hover", false)
.classed("line-selected", false);
/* room buttons */
rooms = roomcontainer.selectAll("div")
.data(d3.map(data, function(d) {return d.Room; }).keys());
rooms.enter()
.append("div")
.attr("class", function(d) { return "room " + school.replace(/\W+/g, '') + " " + d.replace(/\W+/g, ''); })
.text(function(d) { return d; })
.on("click", click);
/* month charts -- see if this can be streamlined or cleaned up at all */
tempnesteddata = d3.nest()
.key(function(d) { return d.Month; })
.entries(data);
keys = d3.map(data, function(d) {return d.Month; }).keys(); //get school months
keys.forEach(function(d, i) { //for each month in school
var schoolm = d;
var mdata = tempnesteddata[i].values; //get values array
allMonths.forEach(function(d, i) { //for each month in calendar year
var allm = allMonths[i];
if (schoolm == allm) { //if months match draw path
tempnestedbyalias = d3.nest()
.key(function(d) { return d.Room; })
.entries(mdata);
svg = monthcontainer.select("#" + schoolm)
.data(tempnestedbyalias);
svg.append("text")
.attr("class", "sm-axis-label " + schoolm)
.attr("x", smw/2)
.attr("y", smh-3)
.style("text-anchor", "middle")
.text(schoolm)
.classed("label-selected", (schoolm == month))
.on("click", changemonth);
svg.selectAll(".chart." + schoolm)
.data(tempnestedbyalias)
.enter().append("path")
.attr("class", function(d) { return "sm line " + schoolm + " " + school.replace(/\W+/g, '') + " " + d.key.replace(/\W+/g, ''); })
.attr("d", function(d) { return smlinefunction(d.values); });
}
});
});
minTemp = d3.min(data, function(d) { return d.Temp_F; });
maxTemp = d3.max(data, function(d) { return d.Temp_F; });
yScale.domain([minTemp-5, maxTemp+1]);
d3.transition()
.duration(altKey ? 7500 : 750)
.each(update);
}
function changemonth() {
var dis = d3.select(d3.event.target);
month = dis.text();
d3.select(".label-selected").classed("label-selected", false);
d3.select(".sm-axis-label." + month).classed("label-selected", true);
d3.transition()
.duration(800)
.each(update);
}
function mouseover(d) {
d3.selectAll(".line") // dim all lines
.style("opacity", 0.2);
d3.selectAll(".line." + d.Room.replace(/\W+/g, ''))
.classed("line-hover", true)
.style("opacity", 0.9);
d3.select("#main-x-axis").selectAll(".tick")
.attr("display", "none");
focus.attr("transform", "translate(" + xScale(d.Time) + "," + yScale(d.Temp_F) + ")");
focus.select(".focus-label")
.text(d.Room + ": " + d3.round(d.Temp_F,2) + "ºF");
focus.select(".focus-line")
.attr("y1", 0)
.attr("y2", h - yScale(d.Temp_F));
focus.select(".focus-time")
.attr("y", h - yScale(d.Temp_F) + 12)
.text(parser(d.Time));
}
function mouseout(d) {
d3.selectAll(".line")
.style("opacity", null);
d3.selectAll("." + d.Room.replace(/\W+/g, ''))
.classed("line-hover", false)
.style("opacity", null);
d3.select("#main-x-axis").selectAll(".tick")
.attr("display", null);
focus.attr("transform", "translate(-100, -100)");
}
function click(d) {
d3.select(this).classed("room-selected", !d3.select(this).classed("room-selected"));
var active = d3.select(this).classed("room-selected") ? true : false;
d3.selectAll(".line." + d.replace(/\W+/g, '') + "." + school.replace(/\W+/g, ''))
.classed("line-selected", active);
}
function wordmouseover(ref) {
var word = ref.innerText || ref.textContent;
var selection;
if (word == "month") { selection = ".sm-axis-label"; }
else if (word == "school") { selection = "select"; }
else if (word == "classrooms") { selection = ".room"; }
d3.selectAll(selection)
.style("color", "#D93333", "important"); //doesn't work for month???
}
function wordmouseout(ref) {
var word = ref.innerText || ref.textContent;
var selection;
if (word == "month") { selection = ".sm-axis-label"; }
else if (word == "school") { selection = "select"; }
else if (word == "classrooms") { selection = ".room"; }
d3.selectAll(selection)
.style("color", null);
}
/*------ INITIAL DRAW ---------*/
var linesvg, focus, voronoiGroup;
function init() {
allmonthsvgs.append("g") //small multiples x-axis
.attr("class", "sm x axis")
.attr("transform", "translate(0," + smh + ")")
.call(smxAxis);
allmonthsvgs.append("g") //small multiples 85 degree threshold
.attr("class", "sm x axis threshold")
.attr("transform", "translate(0," + smyScale(85) + ")")
.call(smxAxis.tickFormat(""));
allmonthsvgs.append("g") //small multiples school hours
.attr("class", "school-hours")
.append("rect")
.attr("x", smxScale(parser.parse("08:00")))
.attr("y", 0)
.attr("width", smxScale(parser.parse("06:00")))
.attr("height", smh);
chart.append("g") //x-axis
.attr("class", "main x axis")
.attr("id", "main-x-axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.append("text")
.attr("class", "main-axis-label")
.attr("x", w-6)
.attr("y", -6)
.attr("dx", ".71em")
.style("text-anchor", "end")
.text("average day");
yScale.domain([70,100]);
chart.append("g") //y-axis
.attr("class", "main y axis")
.attr("id", "main-y-axis")
.attr("transform", "translate(0,0)")
.call(yAxis)
.append("text")
.attr("class", "main-axis-label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("temperature");
chart.append("g") //85 degree threshold
.attr("class", "main x axis threshold")
.attr("transform", "translate(0," + yScale(85) + ")")
.call(xAxis.tickFormat("").tickSize(0));
var annotations = chart.append("g")
.attr("class", "annotations");
annotations.append("rect") //school hours
.attr("class", "school-hours")
.attr("x", xScale(parser.parse("08:00")))
.attr("y", 0)
.attr("width", xScale(parser.parse("06:00")))
.attr("height", h);
annotations.append("text") //school hours annotation
.attr("class", "main-axis-label")
.attr("x", xScale(parser.parse("11:00")))
.attr("y", h-6)
.text("A school day typically runs from 8 am to 2 pm.")
.style("text-anchor", "middle");
annotations.append("text") //large month label
.attr("class", "selected-month-text")
.attr("x", w)
.attr("y", h-16)
.text(month)
.style("text-anchor", "end");
linesvg = chart.append("g") //lines
.attr("class", "lines");
focus = chart.append("g") //mouseovers
.attr("class", "focus")
.attr("transform", "translate(-100,-100)");
voronoiGroup = chart.append("g") //voronoi
.attr("class", "voronoi");
}
</script>
</body>
</html>
https://d3js.org/d3.v3.min.js
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js
https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js