chart = {
let xmin = 0.45;
let xmax = 0.65;
let ymin = 0.47;
let ymax = 1;
let w = 0.9 * width;
let h = w * 0.6;
let pad = 35;
let xScale = d3
.scaleLinear()
.domain([xmin, xmax])
.range([pad, w - pad]);
let yScale = d3
.scaleLinear()
.domain([ymin, ymax])
.range([h - pad, pad]);
let div = d3.create("div").style("width", `${w}px`).style("height", `${h}px`);
let chart = div.append("svg").attr("width", w).attr("height", h);
let lines = chart.append("g");
lines
.append("line")
.attr("x1", xScale(0.5))
.attr("x2", xScale(0.5))
.attr("y1", yScale(ymin))
.attr("y2", yScale(ymax))
.style("stroke", "black")
.style("stroke-width", 1)
.attr("stroke-dasharray", "2 3");
let pts = [];
let x0 = 0.5006;
let y0 = 0.58;
let circles = chart.append("g");
for (let [year, d] of data) {
let all_electoral_votes = d.map((o) => parseInt(o["Electoral Votes"]));
let electoral_1st = all_electoral_votes[0];
let electoral_2nd = all_electoral_votes[1];
let total_electoral_votes = electoral_1st + electoral_2nd;
let electoral_winner_electoral_proportion =
electoral_1st / total_electoral_votes;
let all_popular_votes = d.map((o) => parseInt(o["Popular Votes"]));
let electoral_1st_popular_vote = all_popular_votes[0];
let electoral_2nd_popular_vote = all_popular_votes[1];
let total_popular_votes =
electoral_1st_popular_vote + electoral_2nd_popular_vote;
let electoral_winner_popular_proportion =
electoral_1st_popular_vote / total_popular_votes;
let x = electoral_winner_popular_proportion;
let y = electoral_winner_electoral_proportion;
pts.push([x, y]);
let close_class;
if (Math.abs(x - x0) < 0.005 && Math.abs(y - y0) < 0.005) {
close_class = "close";
} else {
close_class = "far";
}
let circle = circles
.append("circle")
.attr("class", close_class)
.attr("cx", xScale(x))
.attr("cy", yScale(y))
.attr("r", 3)
.style("fill", "#ccc")
.style("stroke", "black")
.style("stroke-width", "0.5px")
.on("mouseenter", function () {
d3.select(this).attr("r", 5).style("stroke-width", "1.5px");
})
.on("mouseleave", function () {
d3.select(this).attr("r", 3).style("stroke-width", "0.5px");
});
tippy(circle.node(), {
allowHTML: true,
content: `
<div>${year}</div>
<div>Winner: ${d[0]["Candidate"]}</div>
<div style="margin-left: 10px">Popular percentage: ${d3.format(
"0.2%"
)(x)}</div>
<div style="margin-left: 10px">Electoral percentage: ${d3.format(
"0.2%"
)(y)}</div>
<div>Loser: ${d[1]["Candidate"]}</div>`
});
}
chart.selectAll(".close").each(function (_, i) {
let c = d3.select(this);
let this_y = c.attr("cy");
c.attr("cy", parseFloat(this_y) + (-1) ** i * 4);
});
let regress = d3.regressionLinear().domain([xmin, xmax])(pts);
let active = true;
lines
.append("line")
.attr("x1", xScale(regress[0][0]))
.attr("x2", xScale(regress[1][0]))
.attr("y1", yScale(regress[0][1]))
.attr("y2", yScale(regress[1][1]))
.style("stroke", "black")
.style("stroke-width", 1);
chart
.append("g")
.attr("transform", `translate(0, ${h - pad})`)
.call(d3.axisBottom(xScale));
chart
.append("g")
.attr("transform", `translate(${pad})`)
.call(d3.axisLeft(yScale));
let tree = d3.quadtree(pts);
console.log(tree.find(0.5, 0.5, 1));
chart
.on("mousemove", function (evt) {
chart.selectAll(".predict").remove();
let mouse_x = xScale.invert(evt.layerX);
let mouse_y = yScale.invert(evt.layerY);
if (tree.find(mouse_x, mouse_y, 0.01) == undefined) {
let predict_y = regress.predict(mouse_x);
if (Math.abs(mouse_y - predict_y) < 0.01) {
console.log(mouse_x);
chart
.append("circle")
.attr("class", "predict")
.attr("cx", evt.layerX)
.attr("cy", yScale(predict_y))
.attr("r", 3)
.style("fill", "#d00")
.style("stroke", "black")
.style("stroke-width", "1px");
chart
.append("text")
.attr("class", "predict")
.attr("pointer-events", "none")
.attr("x", evt.layerX)
.attr("y", yScale(predict_y))
.attr("text-anchor", evt.layerX < 0.7 * w ? "start" : "end")
.text(
`input: ${d3.format("0.3f")(
xScale.invert(evt.layerX)
)}, prediction: ${d3.format("0.3f")(yScale.invert(evt.layerY))}`
);
}
}
})
.on("mouseleave", function () {
chart.selectAll(".predict").remove();
});
div.attr("a", regress.a);
div.attr("b", regress.b);
return div.node();
}