window.drawWeatherGraph = function (graphSel, fig_height, fig_width){ var threshold = .4 var thresholds = [0, .2, .4, .6, .8, 1].map((val, i) => { var isLocked = val == 0 || val == 1 return {val, i, isLocked, origVal: val} }) var c = d3.conventions({ sel: graphSel.html('').append('div'), height: fig_height, totalWidth: fig_width, margin: {top: 100, bottom: 100} }); var {predictionSel, weatherGroupSel} = (function(){ c.y.domain([0,9]).clamp(true); // x-Axis c.xAxis.ticks(5).tickFormat(d3.format('.2f')) c.yAxis.ticks(0) d3.drawAxis(c) c.svg.select('.x') .translate(-40, 1) .selectAll('line').translate(20, 1) // x-Axis label c.svg.append('text.axis-label') .translate([c.width/2, -50]) .at({textAnchor: 'middle'}) .at({fill: '#000', fontSize: 14}) .text('Model Score'); // Weather icons var weatherGroupSel = c.svg.appendMany('g.weatherdata', weatherdata) .translate(d => [c.x(d.score), c.y(d.h)]) //.call(d3.attachTooltip) // .on("mouseover", function(d) { // ttSel.html(""); // var gtSel = ttSel.append("div").html(`ground truth: ${d.label}`); // ttSel.classed("tt-text", true); // }) weatherGroupSel.append('text.icon') .text(function(d,i){return emojis[d.label];}) .at({fontSize: 18, textAnchor: 'middle', dy: 8}) // Add prediction circles weatherGroupSel.append('circle.prediction') .at({cx: 0, cy: 0, r: 14, opacity: 0, fillOpacity: 0, stroke: 'red'}); weatherGroupSel.append('path.prediction') .at({d: d => ['M', -10, 10, 'L', 10, -10].join(' '), stroke: 'red', opacity: 0}) var predictionSel = c.svg.selectAll('.prediction'); return {predictionSel, weatherGroupSel} })() var {thresholdSel, messageSel, setThreshold} = (function(){ var thresholdSel = c.svg.append('g.threshold') var thresholdGroupSel = thresholdSel.append('g') .call(d3.drag().on('drag', () => renderThreshold(c.x.invert(d3.clamp(0, d3.event.x, c.width)))) ) var thesholdTextSel = thresholdGroupSel.append('g.axis').append('text') .at({ textAnchor: 'middle', dy: '.33em', y: c.height + 30 }) .text('Threshold') var rw = 16 thresholdGroupSel.append('rect') .at({ width: rw, x: -rw/2, y: -10, height: c.height + 30, fillOpacity: .07, }) var pathSel = thresholdGroupSel.append('path') .at({ stroke: '#000', strokeDasharray: '2 2', fill: 'none', d: `M 0 -10 V ` + (c.height + 20), }) var accuracyValBox = thresholdSel.append('rect.val-box') .at({width: 55, height: 20, x: c.width/2 + 32.5, y: c.height + 65, rx: 3, ry: 3}) var accuracySel = thresholdSel.append('text.big-text') .at({x: c.width/2 - 10, y: c.height + 80, textAnchor: 'middle'}) var accuracyValSel = thresholdSel.append('text.val-text') .at({x: c.width/2 + 60, y: c.height + 80, textAnchor: 'middle'}) var messageSel = thresholdSel.append('text.tmessage') .at({x: c.width/2, y: c.height + 120, textAnchor: 'middle'}) function renderThreshold(t){ if (isNaN(t)) return // TODO debug this thresholdGroupSel.translate(c.x(t), 0) predictionSel.at({opacity: d => isClassifiedCorrectly(d, t) ? 0 : 1}) var acc = d3.mean( weatherdata, d => isClassifiedCorrectly(d, t) ) accuracySel.text('Accuracy: '); accuracyValSel.text(d3.format('.1%')(acc)) messageSel.text('Try dragging the threshold to find the highest accuracy.') thesholdTextSel.text('Threshold: ' + d3.format('.2f')(t)) threshold = t function isClassifiedCorrectly(d,t) { return d.score >= t ? d.label == 1 : d.label == 0; }; } renderThreshold(threshold) var timer = null function setThreshold(newThreshold, duration){ var interpolateFn = d3.interpolate(threshold, newThreshold) if (timer) timer.stop() timer = d3.timer(ms => { var t = Math.min(ms/duration, 1) if (t == 1) timer.stop() renderThreshold(interpolateFn(t)) }) } return {thresholdSel, messageSel, setThreshold} })() function drawTrueLegend(c){ var truthAxis = c.svg.append('g').translate([fig_width + 40, 1]) truthAxis.append('text.legend-title').text('Truth') // TODO: Maybe more of a label? "what actually happened?" or just remove this legend .at({textAnchor: 'middle', fontWeight: 500, x: 20}) truthAxis.append('g').translate([20, 40]) .append('text.legend-text').text('Sunny').parent() .at({fontSize: 15}) .append('text').text(emojis[0]) .at({fontSize: 25, x: -30, y: 5}) truthAxis.append('g').translate([20, 80]) .append('text.legend-text').text('Rainy').parent() .at({fontSize: 15}) .append('text').text(emojis[1]) .at({fontSize: 25, x: -30, y: 5}) } drawTrueLegend(c); var {thresholdsGroupSel, renderThresholds, setThresholds} = (function(){ var valsCache = [] var drag = d3.drag() .on('drag', function(){ var val = d3.clamp(0, c.x.invert(d3.mouse(c.svg.node())[0]), 1) // Force thresholds to stay sorted valsCache[valsCache.activeIndex] = val _.sortBy(valsCache).forEach((val, i) => thresholds[i].val = val) renderThresholds() }) .on('start', d => { valsCache = thresholds.map(d => d.val) valsCache.activeIndex = d.i }) var thresholdsGroupSel = c.svg.append('g') thresholdsGroupSel.append('text.axis-label') .text('Calibrated Model Score') .translate([c.width/2, c.height + 50]) .at({textAnchor: 'middle'}) .at({fill: '#000', fontSize: 14}) thresholdsSel = thresholdsGroupSel.appendMany('g.thresholds', thresholds) .call(drag) .st({pointerEvents: d => d.isLocked ? 'none' : ''}) thresholdsSel.append('g.axis').append('text') .at({ textAnchor: 'middle', dy: '.33em', y: c.height + 20 }) .text(d => d3.format('.2f')(d.origVal)) var rw = 16 thresholdsSel.append('rect') .at({ width: rw, x: -rw/2, height: c.height + 10, fillOpacity: d => d.isLocked ? 0 : .07, }) var pathSel = thresholdsSel.append('path') .at({ stroke: '#000', strokeDasharray: '2 2', fill: 'none', }) function renderThresholds(){ if (thresholds.some(d => isNaN(d.val))) return thresholdsSel .translate(d => c.x(d.val) + .5, 0) pathSel.at({ d: d => [ 'M', 0, c.height + 10, 'L', 0, 0, 'L', c.x(d.origVal - d.val), -12, ].join(' ') }) if (window.calibrationCurve) calibrationCurve.renderBuckets() } renderThresholds() var timer = null function setThresholds(newThresholds, duration){ var interpolateFns = thresholds .map((d, i) => d3.interpolate(d.val, newThresholds[i])) if (timer) timer.stop() timer = d3.timer(ms => { var t = Math.min(ms/duration, 1) if (t == 1) timer.stop() thresholds.forEach((d, i) => d.val = interpolateFns[i](t)) renderThresholds() }) } return {thresholdsGroupSel, renderThresholds, setThresholds} })() return {c, thresholdSel, messageSel, setThreshold, predictionSel, thresholds, thresholdsGroupSel, renderThresholds, setThresholds, weatherGroupSel}; } if (window.init) window.init()