plotDecompositionBar()

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);
  }