in code/bias-variance/js/ErrorBar.js [336:456]
plotDecompositionBar(data) {
const that = this;
let t = transition().delay(100).duration(600);
// draw same data 3 times, with slightly different errors
// scale y-axis to full
const maxError = max(data, (d) => +d.error);
// set new band xscale for decomposition values
this.xScale.domain([0, maxError]);
// this.xScale.domain([0, maxError + maxError * 0.1]);
this.xAxisGroup
.transition(t)
.call(axisBottom(this.xScale).tickSizeOuter(0).ticks(4));
// update y-axis
this.yScale.domain(["Test"]);
this.yAxisGroup.transition(t).call(axisLeft(this.yScale).tickSizeOuter(0));
// join
this.rects = this.svg.selectAll(`rect.errorBar`).data(data, (d) => d.error);
// exit
this.rects
.exit()
.transition(t)
.attr("height", 0)
.attr("y", this.HEIGHT)
.remove();
// update
this.rects
.transition(t)
.attr("x", (d) => this.xScale(0))
.attr("y", (d) => this.yScale(d.name))
.attr("width", (d) => this.xScale(maxError))
.attr("height", this.yScale.bandwidth())
.attr("id", "decomposition")
.style("opacity", 1);
// hide x-axis group (hide incase scroll up)
this.hideAxes();
// DECOMPOSITION
this.decomp = [
{ name: "Test", error: "Bias2", value: 0.33 * maxError },
{ name: "Test", error: "Variance", value: 0.33 * maxError },
{ name: "Test", error: "Noise", value: 0.33 * maxError },
];
this.biasMap = (d) => {
return d === "Bias2" ? "Bias²" : d;
};
// axis for decomp positions
this.decompAxis = scaleBand()
.range([0, this.xScale(maxError)])
.padding(0)
.domain(["Bias2", "Variance", "Noise"]);
// save value for error offset
this.biasX =
that.WIDTH / 10 -
this.decompAxis("Bias2") +
this.xScale(0.33 * maxError) / 2;
const stackColor = scaleOrdinal()
.domain(this.decomp, (d) => d.error)
.range(["coral", "skyblue", "teal"]);
this.decompLegend = this.svg
.append("g")
.attr("class", "decomp-legend")
.attr("transform", "translate(0,0)");
// create g elements to hold decomp rects and labels
this.decompGs = this.decompLegend
.selectAll("g.decompG")
.data(this.decomp)
.enter()
.append("g")
.attr("class", "decompG");
// append rectangles to decomp G
const stackedRects = this.decompGs
.append("rect")
.attr("class", "stacked")
.attr("fill", (d) => stackColor(d.error))
.attr("id", (d) => `rect-${d.error}`)
.attr("x", (d) => this.decompAxis(d.error))
.attr("y", (d) => this.yScale(d.name))
.attr("height", this.yScale.bandwidth())
.attr("width", (d) => this.xScale(+d.value))
.style("opacity", 0);
this.decompGs
.append("text")
.attr("class", "decomp-text")
.attr("x", (d) => this.decompAxis(d.error))
.attr("y", (d) => this.yScale.bandwidth() / 2 + this.MARGIN.TOP)
.attr("dx", (d) => this.xScale(+d.value) / 2)
.text((d) => this.biasMap(d.error))
.style("opacity", 0);
// init rectangle borders around text (for labels later)
this.textBox = {};
selectAll(".decomp-text").each(function (d) {
const textBox = this.getBBox();
that.textBox[d.error] = textBox;
});
// reset text-anchor to middle
// we have to reset it because otherwise the later transition to legend won't work
selectAll(".decomp-text").attr("text-anchor", "middle");
// line up transitions
const decompT = transition().delay(600).ease(easeBack).duration(500);
// transition everything into view
selectAll("rect.stacked").transition(decompT).style("opacity", 0.7);
selectAll(".decomp-text").transition(decompT).style("opacity", 1);
this.rects.transition(decompT).style("opacity", 0);
}