forked from BenjiFischman‘s block:
<!DOCTYPE html>
<html>
<head>
<title>Benji's First Block</title>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="local.js"></script>
<link rel='stylesheet' href='style.css'/>
</head>
<body>
<div class="graph-wrapper" id="one"></div>
</body>
</html>
$(function(){
var parseDate = d3.timeParse("%b %d, '%y");
var yObjs = {
"Engagment" : {column: "Engagment"},
"Diversity" : {column: "Diversity"},
"Daily_Message" : {column: "Daily_Message"},
"Relapses" : {column: "Relapses"}
}
var axisLabels = {xAxis: 'Time', yAxis: 'Usage'};
var xName = "Time";
var parseTime = d3.timeParse("%Y-%m-%d %H:%M:%S");
d3.json('output.json', function(err, data){
data.forEach(function (d) {
d.Time = d.Time
switch(d.dCode){
case "RELAPSE_DURATION_1-10":
d.relapse = 1;
break;
case "RELAPSE_DURATION_11-30":
d.relapse = 2;
break;
case "RELAPSE_DURATION_30+":
d.relapse = 3;
break;
default:
d.relapse = 0;
}
d.Time = parseTime(d.Time);
d.Engagment = +d.Engagment;
d.Diversity = +d.aDiversity;
d.Daily_Message = +d.dailyMsg;
d.relapse = d.relapse / 3;
d.Relapses = d.relapse;
})
var graphOne = createGraph(data, xName, yObjs, axisLabels);
graphOne.bind("#one");
graphOne.render();
});
});
function createGraph(dataset, xName, yObjs, axisLables){
var color = d3.scaleOrdinal(d3.schemeCategory10);
var graphObj = {};
var relapse_cols = ["#ff0000", "#ab0000", "#460000"];
graphObj.relapse_cols = d3.scaleOrdinal().range(relapse_cols);
graphObj.data = dataset;
graphObj.relapse_data = [];
graphObj.data.forEach(function(d){
if(d.relapse != 0){
var x = d;
d.y = 1;
graphObj.relapse_data.push(x);
}
});
graphObj.xAxisLable = axisLables.xAxis;
graphObj.yAxisLable = axisLables.yAxis;
var marigin = {top: 30,
right: 20,
bottom: 30,
left: 50};
graphObj.marigin = marigin;
var width = 1000 - marigin.left - marigin.right;
graphObj.width = width;
var height = 300 - marigin.top - marigin.bottom;
graphObj.height = height;
var parseTime = d3.timeParse("%Y-%m-%d %H:%M:%S");
// So we can pass the x and y as strings when creating the function
graphObj.xFunct = function(d){return d[xName]};
// For each yObjs argument, create a yFunction
function getYFn(column) {
return function (d) {
return d[column];
};
}
// Object instead of array
graphObj.yFuncts = [];
for (var y in yObjs) {
yObjs[y].name = y;
yObjs[y].yFunct = getYFn(yObjs[y].column); //Need this list for the ymax function
graphObj.yFuncts.push(yObjs[y].yFunct);
}
graphObj.formatAsNumber = d3.format(".0f");
graphObj.formatAsDecimal = d3.format(".2f");
graphObj.formatAsLongDecimal = d3.format(".4f");
graphObj.formatAsTime = d3.timeFormat("%b %d, '%y");
graphObj.formatAsFloat = function (d) {
if (d % 1 !== 0) {
return d3.format(".2f")(d);
} else {
return d3.format(".0f")(d);
}
};
graphObj.xFormatter = graphObj.formatAsTime;
graphObj.yFormatter = graphObj.formatAsLongDecimal;
graphObj.bisectYear = d3.bisector(graphObj.xFunct).left; //< Can be overridden in definition
//Create scale functions
graphObj.xScale = d3.scaleTime().range([0, graphObj.width]).domain(d3.extent(graphObj.data, graphObj.xFunct)); //< Can be overridden in definition
// Get the max of every yFunct
graphObj.max = function (fn) {
return d3.max(graphObj.data, fn);
};
var maxDomain = d3.max(graphObj.yFuncts.map(graphObj.max));
//compute max for each variable and divide each series by its max value
graphObj.yScale = d3.scaleLinear().range([graphObj.height, 0]).domain([0, maxDomain]);
graphObj.formatAsYear = d3.format(""); //<---------- might need to adjust this fpr axis labels
graphObj.xAxis = d3.axisBottom().scale(graphObj.xScale).tickFormat(graphObj.xFormatter);
graphObj.yAxis = d3.axisLeft().scale(graphObj.yScale).tickFormat(graphObj.yFormatter);
// Build line building functions
function getYScaleFn(yObj) {
return function (d) {
if (d.relapse > 0) {
return graphObj.yScale(maxDomain);
} else {
return graphObj.yScale(yObjs[yObj].yFunct(d));
}
};
}
for (var yObj in yObjs) {
if (yObj == "Relapses"){
yObjs[yObj].line = d3.line()
.x(function (d) {
return graphObj.xScale(graphObj.xFunct(d));
})
.y(getYScaleFn(yObj))
.curve(d3.curveBasis);
}else{
yObjs[yObj].line = d3.line()
.x(function (d) {
return graphObj.xScale(graphObj.xFunct(d));
})
.y(getYScaleFn(yObj))
.curve(d3.curveBasis);
}}
graphObj.svg;
// Change graph size according to window size
graphObj.update_svg_size = function () {
graphObj.width = parseInt(graphObj.graphDiv.style("width"), 10) - (graphObj.marigin.left + graphObj.marigin.right);
graphObj.height = parseInt(graphObj.graphDiv.style("height"), 10) - (graphObj.marigin.top + graphObj.marigin.bottom);
/* Update the range of the scale with new width/height */
graphObj.xScale.range([0, graphObj.width]);
graphObj.yScale.range([graphObj.height, 0]);
if (!graphObj.svg) {return false;}
/* Else Update the axis with the new scale */
graphObj.svg.select('.x.axis').attr("transform", "translate(0," + graphObj.height + ")").call(graphObj.xAxis);
graphObj.svg.select('.x.axis .label').attr("x", graphObj.width / 2);
graphObj.svg.select('.y.axis').call(graphObj.yAxis);
graphObj.svg.select('.y.axis .label').attr("x", -graphObj.height / 2);
/* Force D3 to recalculate and update the line */
for (var y in yObjs) {
yObjs[y].path.attr("d", yObjs[y].line);
console.log("y check"+ JSON.stringify(y));
}
d3.selectAll(".focus.line").attr("y2", graphObj.height);
graphObj.graphDiv.select('svg').attr("width", graphObj.width + (graphObj.marigin.left + graphObj.marigin.right)).attr("height", graphObj.height + (graphObj.marigin.top + graphObj.marigin.bottom));
graphObj.svg.select(".overlay").attr("width", graphObj.width).attr("height", graphObj.height);
return graphObj;
};
graphObj.bind = function (selector) {
graphObj.mainDiv = d3.select(selector);
// Add all the divs to make it centered and responsive
graphObj.mainDiv.append("div").attr("class", "inner-wrapper").append("div").attr("class", "outer-box").append("div").attr("class", "inner-box");
graphSelector = selector + " .inner-box";
graphObj.graphDiv = d3.select(graphSelector);
d3.select(window).on('resize.' + graphSelector, graphObj.update_svg_size);
graphObj.update_svg_size();
return graphObj;
};
graphObj.render = function () {
//Create SVG element
graphObj.svg = graphObj.graphDiv.append("svg").attr("class", "graph-area").attr("width", graphObj.width + (graphObj.marigin.left + graphObj.marigin.right)).attr("height", graphObj.height + (graphObj.marigin.top + graphObj.marigin.bottom)).append("g").attr("transform", "translate(" + graphObj.marigin.left + "," + graphObj.marigin.top + ")");
// Draw Lines
for (var y in yObjs) {
if(y == "Relapses"){
//standard approach
//Relapse logic handled in line generator
//if else statment left for debugging purposes
yObjs[y].path = graphObj.svg.append("path").datum(graphObj.data).attr("class", "line").attr("d", yObjs[y].line).style("stroke-width", "2px").style("stroke", color(y)).attr("data-series", y).on("mouseover", function () {
focus.style("display", null);
}).on("mouseout", function () {
focus.transition().delay(700).style("display", "none");
}).on("mousemove", mousemove);
} else{
yObjs[y].path = graphObj.svg.append("path").datum(graphObj.data).attr("class", "line").attr("d", yObjs[y].line).style("stroke", color(y)).attr("data-series", y).on("mouseover", function () {
focus.style("display", null);
}).on("mouseout", function () {
focus.transition().delay(700).style("display", "none");
}).on("mousemove", mousemove);
}}
// Draw Axis
graphObj.svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + graphObj.height + ")").call(graphObj.xAxis).append("text").attr("class", "label").attr("x", graphObj.width / 2).attr("y", 30).style("text-anchor", "middle").text(graphObj.xAxisLable);
graphObj.svg.append("g").attr("class", "y axis").call(graphObj.yAxis).append("text").attr("class", "label").attr("transform", "rotate(-90)").attr("y", -42).attr("x", -graphObj.height / 2).attr("dy", ".71em").style("text-anchor", "middle").text(graphObj.yAxisLable);
//Draw tooltips
var focus = graphObj.svg.append("g").attr("class", "focus").style("display", "none");
for (var y in yObjs) {
yObjs[y].tooltip = focus.append("g");
yObjs[y].tooltip.append("circle").attr("r", 5);
yObjs[y].tooltip.append("rect").attr("x", 8).attr("y","-5").attr("width",22).attr("height",'0.75em');
yObjs[y].tooltip.append("text").attr("x", 9).attr("dy", ".35em");
}
// Year label
focus.append("text").attr("class", "focus year").attr("x", 9).attr("y", 7);
// Focus line
focus.append("line").attr("class", "focus line").attr("y1", 0).attr("y2", graphObj.height);
//Draw legend
var legend = graphObj.mainDiv.append('div').attr("class", "legend");
for (var y in yObjs) {
series = legend.append('div');
series.append('div').attr("class", "series-marker").style("background-color", color(y));
series.append('p').text(y);
yObjs[y].legend = series;
}
// Overlay to capture hover
graphObj.svg.append("rect").attr("class", "overlay").attr("width", graphObj.width).attr("height", graphObj.height).on("mouseover", function () {
focus.style("display", null);
}).on("mouseout", function () {
focus.style("display", "none");
}).on("mousemove", mousemove);
return graphObj;
function mousemove() {
//this is wht the NAN error is happening
var x0 = graphObj.xScale.invert(d3.mouse(this)[0]), i = graphObj.bisectYear(dataset, x0, 1), d0 = graphObj.data[i - 1], d1 = graphObj.data[i];
try {
var d = x0 - graphObj.xFunct(d0) > graphObj.xFunct(d1) - x0 ? d1 : d0;
} catch (e) { return;}
minY = graphObj.height;
for (var y in yObjs) {
// console.log("tooltip y check "+ y);
yObjs[y].tooltip.attr("transform", "translate(" + graphObj.xScale(graphObj.xFunct(d)) + "," + graphObj.yScale(yObjs[y].yFunct(d)) + ")");
yObjs[y].tooltip.select("text").text(graphObj.yFormatter(yObjs[y].yFunct(d)));
minY = Math.min(minY, graphObj.yScale(yObjs[y].yFunct(d)));
}
//console.log(minY);
focus.select(".focus.line").attr("transform", "translate(" + graphObj.xScale(graphObj.xFunct(d)) + ")").attr("y1", minY);
focus.select(".focus.year").text("Date: " + graphObj.xFormatter(graphObj.xFunct(d)));
}
};
return graphObj;
}
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}
ul {
list-style-type:none;
}
#user-wrapper {
position:relative;
margin: 5% auto 5% auto;
min-height: 250px;
overflow: hidden;
}
#summary-wrapper{
float: left;
min-height: 250px;
width:20%;
overflow: hidden;
}
.graph-wrapper{
float:right;
overflow: hidden;
min-height: 250px;
width:80%;
}
.tooltip {
background: #eee;
box-shadow: 0 0 5px #999999;
color: #333;
display: none;
font-size: 12px;
left: 130px;
padding: 10px;
position: absolute;
text-align: center;
top: 95px;
width: 80px;
z-index: 10;
}
.axis { font: 14px sans-serif; }
/*.line {
fill: none;
stroke: steelblue;
shape-rendering: crispEdges;
stroke-width: 2px;
}*/
/*#dailyMsg_plot {
fill: none;
stroke: "#28d41c" !important;
shape-rendering: crispEdges;
stroke-width: 2px;
}
#diversity_plot {
fill: none;
stroke: "#7721d9" !important;
shape-rendering: crispEdges;
stroke-width: 2px;
}
*/
.graph-wrapper {
float:center;
max-width: 950px;
min-width: 304px;
margin: 0 auto;
background-color: #FAF7F7;
}
.graph-wrapper .inner-wrapper {
position: relative;
padding-bottom: 50%;
width: 100%;
}
.graph-wrapper .outer-box {
position: absolute;
top: 0; bottom: 0; left: 0; right: 0;
}
.graph-wrapper .inner-box {
width: 100%;
height: 100%;
}
.graph-wrapper text {
font-family: sans-serif;
font-size: 11px;
}
.graph-wrapper y-axis text {
rotate: -65;
}
.graph-wrapper p {
font-size: 16px;
margin-top:5px;
margin-bottom: 40px;
}
.graph-wrapper .axis path,
.graph-wrapper .axis line {
fill: none;
stroke: #1F1F2E;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.graph-wrapper .axis path {
stroke-width: 2px;
}
.graph-wrapper .line {
fill: none;
stroke: steelblue;
stroke-width: 5px;
}
.graph-wrapper .legend {
min-width: 200px;
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
font-size: 16px;
padding: 10px 40px;
}
.graph-wrapper .legend > div {
margin: 0px 25px 10px 0px;
flex-grow: 0;
}
.graph-wrapper .legend p {
display:inline;
font-size: 0.8em;
font-family: sans-serif;
font-weight: 600;
}
.graph-wrapper .legend .series-marker {
height: 1em;
width: 1em;
border-radius: 35%;
background-color: crimson;
display: inline-block;
margin-right: 4px;
margin-bottom: -0.16rem;
}
.graph-wrapper .overlay {
fill: none;
pointer-events: all;
}
.graph-wrapper .focus circle {
fill: black;
stroke: black;
stroke-width: 2px;
fill-opacity: 15%;
}
.graph-wrapper .focus rect {
fill: lightblue;
opacity: 0.4;
border-radius: 2px;
}
.graph-wrapper .focus.line {
stroke: steelblue;
stroke-dasharray: 2,5;
stroke-width: 2;
opacity: 0.5;
}
@media (max-width:500px){
.graph-wrapper .line {stroke-width: 3px;}
.graph-wrapper .legend {font-size: 14px;}
}