// our input dataset, cleaned slightly d3.csv("weatherdata.csv", function(dataset) { dataset = _.map(dataset, function(obj) { return { "date": parseFloat(obj.date), "month": parseFloat(obj.month), "temp": parseInt(obj.temp), "index": parseInt(obj.index), "yearmonth": parseFloat(obj.yearmonth), "temp_avg": parseFloat(obj.temp_avg), "year": parseFloat(obj.year) }; }) // split into two; define split to be whatever fraction var trainingset = dataset.slice(0, dataset.length * 0.7); var testset = dataset.slice(dataset.length * 0.7, dataset.length); // for drawing the line, unfortunately necessary in this format as of now var guide = [ {"month": 1, "temp": 0}, {"month": 2, "temp": 0}, {"month": 3, "temp": 0}, {"month": 4, "temp": 0}, {"month": 5, "temp": 0}, {"month": 6, "temp": 0}, {"month": 7, "temp": 0}, {"month": 8, "temp": 0}, {"month": 9, "temp": 0}, {"month": 10, "temp": 0}, {"month": 11, "temp": 0}, {"month": 12, "temp": 0}, {"month": 13, "temp": 0}, ] // to convert numbers -> month names var month_dict = {"1": "Jan.", "2": "Feb.", "3": "March", "4": "April", "5": "May", "6": "June", "7": "July", "8": "Aug.", "9": "Sept.", "10": "Oct.", "11": "Nov.", "12": "Dec."} // conventions and syntax found at: // https://github.com/1wheel/d3-starterkit var ƒ = d3.f; var sel = d3.select('#chart'); var c = d3.conventions({ parentSel: sel, totalWidth: sel.node().offsetWidth, height: 400, margin: {left: 50, right: 50, top: 20, bottom: 30} }); // define min/max for y axis var mintemp = Math.floor((d3.min(dataset, function(d) { return d.temp; }))/10) * 10; var maxtemp = Math.ceil((d3.max(dataset, function(d) { return d.temp; }))/10) * 10; c.x.domain([1, 13]); c.y.domain([mintemp, maxtemp]); var svg = d3.selectAll('#chart').selectAll("svg").selectAll("g").selectAll("g"); // rectangles for guiding user to complete line var rect_color = "rgba(241, 196, 15, 0.4)" for (var i = guide.length - 1; i >= 0; i--) { if (i == guide.length - 1) { c.svg.append('rect') .at({width: c.width/24, height: c.height, x: i*c.width/12 - c.width/24, y: 0, fill: rect_color, class: "monthbar monthbar-"+i}) } else if (i == 0) { c.svg.append('rect') .at({width: c.width/24, height: c.height, x: i*c.width/12, y: 0, fill: rect_color, class: "monthbar monthbar-"+i}) } else { c.svg.append('rect') .at({width: c.width/12, height: c.height, x: i*c.width/12 - c.width/24, y: 0, fill: rect_color, class: "monthbar monthbar-"+i}) } } // draw background rect c.svg.append('rect').at({width: c.width, height: c.height, opacity: 0}) // draw axes, hidden at first var xAxis = d3.axisBottom() .scale(c.x) .tickSize(-c.height) .tickPadding(10) .tickFormat(function(d) { return month_dict[d]; }); var yAxis = d3.axisLeft() .scale(c.y) .tickSize(-c.width) .ticks(5) .tickFormat(function(d) { return d + '🌡️F';}) // .tickFormat(d => d + '°F') var axisSVG = d3.selectAll('#chart').selectAll("svg").selectAll("g") axisSVG.append("g").call(customYAxis) .attr("transform", "translate(0,0)") .attr("class", "axis hiddentext tempYAxis") axisSVG.append("g").call(customXAxis) .attr("transform", "translate(0," + c.height + ")") .attr("class", "axis hiddentext monthXAxis") c.svg.append('text').at({x: 5, y: -5}) .text("Temperature") .attr("transform", "rotate(90)") .attr("class", "hiddentext month-axis") // month Axis for (var i = guide.length - 2; i >= 0; i--) { c.svg.append('text') .at({x: i*c.width/12 + c.width/24, y: c.height + 16, class: "month-axis axis hiddentext"}) .text(month_dict[guide[i].month]) .attr("text-anchor", "middle") } function customYAxis(g) { g.call(yAxis); g.select(".domain").remove(); g.selectAll(".tick:not(:first-of-type) line") .attr("stroke-dasharray", "5,5"); } function customXAxis(g) { g.call(xAxis); g.select(".domain").remove(); g.selectAll(".tick:not(:first-of-type) line") .attr("stroke-dasharray", "5,5"); } // draw training set circles svg.data(trainingset).enter() .append('circle') .attr("class", "training") .attr("cx", function(d) { var temp_x = c.x(d.month); return temp_x; }) .attr("r", function(d) { return 3; }) .attr("cy", function(d) { return c.y(d.temp); }) // .on("mouseover", function(d) { return mouseover(d); }) // .on("mousemove", function(d) { return mousemove(d); }) // .on("mouseout", function(d) { return mouseout(d); }) // draw text to tell readers to draw a line c.svg.append('text').at({x: c.width * 0.75, y: c.height * 0.8}) .text("Draw a line for how") .attr("class", "instructions instructions1") .attr("text-anchor", "middle") c.svg.append('text').at({x: c.width * 0.75, y: c.height * 0.8 + 24}) .text("you think the model will work") .attr("class", "instructions instructions2") .attr("text-anchor", "middle") var textBBox1 = c.svg.select(".instructions1").node().getBBox(); var textBBox2 = c.svg.select(".instructions2").node().getBBox(); c.svg.append('rect') .at({width: Math.max(textBBox1.width, textBBox2.width) + 30, height: textBBox1.height*2 + 30, opacity: 0.8, x: Math.min(textBBox1.x, textBBox2.x) - 15, y: Math.min(textBBox1.y, textBBox2.y) - 10, fill: "white", class: "instructions"}) c.svg.append('text').at({x: c.width * 0.75, y: c.height * 0.8}) .text("Draw a line for how") .attr("class", "instructions instructions1") .attr("text-anchor", "middle") c.svg.append('text').at({x: c.width * 0.75, y: c.height * 0.8 + 24}) .text("you think the model will work") .attr("class", "instructions instructions2") .attr("text-anchor", "middle") var line = d3.area().x(ƒ('month', c.x)).y(ƒ('temp', c.y)) yourDataSel = c.svg.append('path.your-line#your-line') yourData = guide.slice() yourData = yourData.map(function(d){ return {month: d.month, temp: d.temp, defined: 0} }) // function for line interaction drag = d3.drag() .on('drag', function(){ var pos = d3.mouse(this) var month = clamp(1, 13, c.x.invert(pos[0])) var temp = clamp(mintemp, c.y.domain()[1], c.y.invert(pos[1])) yourData.forEach(function(d){ if (Math.abs(d.month - month) < .5){ d.temp = temp d.defined = true // find the empty sections, and highlight them a different color d3.select(".monthbar-"+Math.round(month - 1)) .classed("monthselected", false) .style("fill", "none") } }) yourDataSel.at({d: line.defined(ƒ('defined'))(yourData)}) // if user has a completed line, enable the 'finished' button if (d3.mean(yourData, ƒ('defined')) == 1){ $("#done").prop('disabled', false) changeResiduals(yourData); d3.select("#errorrate") .transition().delay(500).duration(1000) .text("RMSE Error: " + rmseFormat(calculateErrorRate(yourData))); } // if user has drawn a line in the area with the text box, make it disappear if (c.x.invert(pos[0]) > 7) { d3.selectAll(".instructions").transition().attr("visibility", "hidden") } }) c.svg.call(drag) function calculateErrorRate(linedata) { // take in points the line is defined by // calculate the RMSE var errorsum = 0; for (var i = testset.length - 1; i >= 0; i--) { errorsum += (testset[i].temp - findYatX(testset[i].month, linedata))*(testset[i].temp - findYatX(testset[i].month, linedata)) } var errorsum = Math.sqrt(errorsum / testset.length) return errorsum; } // helper function for finding line values function findYatX(x, linedata) { // x is the month (not necceserily an integer) // get the left and right amount var x1 = _.filter(linedata, function(obj) { return obj.month == Math.floor(x) })[0]; var x2 = _.filter(linedata, function(obj) { return obj.month == Math.ceil(x) })[0]; return d3.interpolateNumber(x1.temp, x2.temp)(x - x1.month) } // draw residuals function drawResiduals(linedata) { for (var i = testset.length - 1; i >= 0; i--) { c.svg.append("line") .attr("x1", c.x(testset[i].month)) .attr("x2", c.x(testset[i].month)) .attr("y1", c.y(findYatX(testset[i].month, linedata))) .attr("y2", c.y(findYatX(testset[i].month, linedata))) .transition() .duration(500) .delay(1000) .attr("y2", c.y(testset[i].temp)) .attr("class", function(d) { return "residual-original residual-"+testset[i].date ; }) } } // when user changes the line function changeResiduals(linedata) { for (var i = testset.length - 1; i >= 0; i--) { d3.selectAll(".residual-"+testset[i].date) .transition() .attr("y1", c.y(findYatX(testset[i].month, linedata))) } } rmseFormat = d3.format(".4r"); function clamp(a, b, c){ return Math.max(a, Math.min(b, c)) } // for when person is done guessing // draw line, draw rest of circles, draw residuals, draw axes $("#done").click(function() { if (d3.mean(yourData, ƒ('defined')) == 1){ // somewhat useless, since we disable the button until the condition is satisfied // clipRect.transition().duration(1000).attr('width', c.x(13)) svg.data(testset).enter() .append('circle') .attr("class", "testing") .attr("cx", function(d) { var temp_x = c.x(d.month); return temp_x; }) .attr("r", 0) .attr("cy", function(d) { return c.y(d.temp); }) .transition() .duration(500) // ease in points month by month .delay(function(d) { return 50*(d.month-6); }) .attr('r', 3) $(".tempYAxis").removeClass("hiddentext"); d3.selectAll(".month-axis").style("visibility", "visible"); d3.selectAll(".hiddenuntilcomplete").transition().delay(500) .style("display", "inline-block") .style("visibility", "visible") d3.selectAll("#done") .transition() .style("display", "none"); drawResiduals(yourData) // disable graph interactivity // c.svg.on(".drag", null); } }) // // // // // // // // // // // yearly graph (seasons) // // // // // // // // // // // yearlyDataset = _.filter(_.sortBy(dataset, "yearmonth"), function(obj) { return obj.month == Math.floor(obj.month); }); var selYearly = d3.select('#yearly') var cYearly = d3.conventions({ parentSel: selYearly, totalWidth: Math.min(sel.node().offsetWidth*2, $(window).width()*0.8), // change this to be more consistent / decide what I want height: 200, margin: {left: 50, right: 50, top: 30, bottom: 30} }) var minYearlyTemp = Math.floor((d3.min(dataset, function(d) { return d.temp; }))/10) * 10; var maxYearlyTemp = Math.ceil((d3.max(dataset, function(d) { return d.temp; }))/10) * 10; cYearly.y.domain([minYearlyTemp, maxYearlyTemp]) xScaleYearly = d3.scaleBand() .domain(_.uniq(_.pluck(yearlyDataset, "yearmonth"))) .range([0, cYearly.width]) xScaleYearlyScatter = d3.scaleBand() .domain(_.pluck(dataset, "date").sort()) .range([0, cYearly.width]) // https://bl.ocks.org/mbostock/1087001 // for mouseover events var tooltip = d3.select("body").append("div") .attr("class", "scatterplot-tooltip hiddenuntilcomplete") .style("display", "none"); function mouseover(d) { d3.selectAll(".date-"+d.date) .transition() .attr("r", 10) .attr("opacity", 1) tooltip.style("display", "inline"); } function mousemove(d) { tooltip .text(month_dict[Math.floor(d.month)] + " " + d.date.toString().slice(6,8) + ": " + d.temp + "🌡️F") .style("left", (d3.event.pageX - 34) + "px") .style("top", (d3.event.pageY - 12) + "px"); } function mouseout(d) { tooltip.style("display", "none"); d3.selectAll(".date-"+d.date) .transition() .attr("r", 3) .attr("opacity", 0.2) } // axis functions var xYearlyAxis = d3.axisBottom() .scale(xScaleYearly) .tickSize(-cYearly.height) .tickPadding(10); var yYearlyAxis = d3.axisLeft() .scale(cYearly.y) .tickSize(-cYearly.width) .ticks(6) .tickFormat(function(d) { return d + '🌡️F'; }); function customYYearlyAxis(g) { g.call(yYearlyAxis); g.select(".domain").remove(); g.selectAll(".tick line") .attr("stroke-dasharray", "5,5"); } var axisSVGYearly = d3.selectAll('#yearly').selectAll("svg").selectAll("g") axisSVGYearly.append("g").call(customYYearlyAxis) .attr("transform", "translate(0,0)") .attr("class", "axis tempYAxis") cYearly.svg.append('text').at({x: 5, y: -5}) .text("Temperature") .attr("transform", "rotate(90)") .attr("class", "hiddentext month-axis temperature-text") // Background rectagles to distinguish between years var barwidth = cYearly.width / _.uniq(_.pluck(yearlyDataset, "year")).length; var years = _.uniq(_.pluck(yearlyDataset, "year")); for (var i = 0; i < years.length; i++) { cYearly.svg.append('rect') .at({width: barwidth, height: cYearly.height, opacity: 0.1, x: i*barwidth, y: 0}) .attr("fill", function() { if (i % 2 == 0) { return "white" } else { return "gray" } }) // year labels cYearly.svg.append('text') .at({x: i*barwidth + barwidth/2, y: cYearly.height - 10}) .attr("text-anchor", "middle") .text(years[i]) } // Code for creating a scatter plot var yearlySvg = d3.selectAll('#yearly').selectAll("svg").selectAll("g"); yearlySvg.data(dataset).enter() .append('circle') .attr("class", function(d) { return "date-"+d.date; }) .attr("cx", function(d) { var temp_x = xScaleYearlyScatter(d.date); return temp_x; }) .attr("r", function(d) { return 3; }) .attr("cy", function(d) { return cYearly.y(d.temp); }) .attr("fill", "steelblue") .attr("opacity", 0.2) .on("mouseover", function(d) { return mouseover(d); }) .on("mousemove", function(d) { return mousemove(d); }) .on("mouseout", function(d) { return mouseout(d); }) .attr("transform", function() { return "translate("+50+","+30+")" }) // change to not be hard-coded // create the line for our line chart var yearlyLine = d3.line() .x(function(d) { return xScaleYearly(d.yearmonth); }) .y(function(d) { return cYearly.y(d.temp_avg); }) .curve(d3.curveCatmullRom.alpha(0.5)); cYearly.svg.append("path") .datum(yearlyDataset) .attr("fill", "none") .attr("stroke", "steelblue") .attr("stroke-linejoin", "round") .attr("stroke-linecap", "round") .attr("stroke-width", 2.5) .attr("d", yearlyLine); })