Updating source data based on existing d3 interpolator

I am trying to find out if it is possible to update a data source based on a particular d3 interpolatior, a notebook here.

Before I elaborate on the problem, I don’t know why the observable notebook does not render the div created at last. The original html renders like following in the browser

I am running an interpolator to animate the X axis which is .xAxis>.xAxisBottom>.domain. Now, while this interpolation takes place, can I also update the data source based on the exact same interpolator, which is src2 to capture each change in year, that is const invertVal = scaleX.invert(valX); witihin src2?

To elaborate, I have built 6 div one for each category and I can use the exact same interpolator as X-Axis to display tweened text per div. But I was wondering with the interpolator running, how can I keep on updating src2 based on the same interpolator?

I have tried by passing src2.map((a, i) => (a.year = invertVal)) which does not work.

The motivation behind this is to create an enter-update animation and I came across post1, post2, post3. d3 can update the animation when source data updates and I want to update the source data using this interpolator.

But here, the data source is not updated using any interpolator that had already been used to animate any existing element.

But if I choose to use the interpolator already in play, how do I update the data based on that?

Thank you in advance.

The full code is below

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
  <body>
    <svg></svg>
  </body>
  <script type="text/javascript">
    const data = [{
      Category: "Admin",
      Year: 2012,
      Value: 0.43
    }, {
      Category: "Admin",
      Year: 2013,
      Value: 0.396
    }, {
      Category: "Admin",
      Year: 2014,
      Value: 0.318
    }, {
      Category: "Admin",
      Year: 2015,
      Value: 0.253
    }, {
      Category: "Admin",
      Year: 2016,
      Value: 0.096
    }, {
      Category: "Admin",
      Year: 2017,
      Value: 0.07
    }, {
      Category: "Admin",
      Year: 2018,
      Value: 0.055
    }, {
      Category: "Admin",
      Year: 2019,
      Value: 0.0218
    }, {
      Category: "Admin",
      Year: 2020,
      Value: 0.0172
    }, {
      Category: "Admin",
      Year: 2021,
      Value: 0.018600000000000002
    }, {
      Category: "Admin",
      Year: 2022,
      Value: 0.0166
    }, {
      Category: "Tax",
      Year: 2012,
      Value: 0.0069999999999999993
    }, {
      Category: "Tax",
      Year: 2013,
      Value: 0.012
    }, {
      Category: "Tax",
      Year: 2014,
      Value: 0.025
    }, {
      Category: "Tax",
      Year: 2015,
      Value: 0.038
    }, {
      Category: "Tax",
      Year: 2016,
      Value: 0.049
    }, {
      Category: "Tax",
      Year: 2017,
      Value: 0.05
    }, {
      Category: "Tax",
      Year: 2018,
      Value: 0.06
    }, {
      Category: "Tax",
      Year: 2019,
      Value: 0.0663
    }, {
      Category: "Tax",
      Year: 2020,
      Value: 0.0766
    }, {
      Category: "Tax",
      Year: 2021,
      Value: 0.077800000000000008
    }, {
      Category: "Tax",
      Year: 2022,
      Value: 0.0639
    }, {
      Category: "Depriciation",
      Year: 2012,
      Value: 0.039
    }, {
      Category: "Depriciation",
      Year: 2013,
      Value: 0.059000000000000004
    }, {
      Category: "Depriciation",
      Year: 2014,
      Value: 0.07
    }, {
      Category: "Depriciation",
      Year: 2015,
      Value: 0.08
    }, {
      Category: "Depriciation",
      Year: 2016,
      Value: 0.076
    }, {
      Category: "Depriciation",
      Year: 2017,
      Value: 0.11900000000000001
    }, {
      Category: "Depriciation",
      Year: 2018,
      Value: 0.139
    }, {
      Category: "Depriciation",
      Year: 2019,
      Value: 0.17079999999999998
    }, {
      Category: "Depriciation",
      Year: 2020,
      Value: 0.21559999999999999
    }, {
      Category: "Depriciation",
      Year: 2021,
      Value: 0.1834
    }, {
      Category: "Depriciation",
      Year: 2022,
      Value: 0.22579999999999997
    }, {
      Category: "Amortization",
      Year: 2012,
      Value: 0.004
    }, {
      Category: "Amortization",
      Year: 2013,
      Value: 0.008
    }, {
      Category: "Amortization",
      Year: 2014,
      Value: 0.016
    }, {
      Category: "Amortization",
      Year: 2015,
      Value: 0.025
    }, {
      Category: "Amortization",
      Year: 2016,
      Value: 0.036000000000000004
    }, {
      Category: "Amortization",
      Year: 2017,
      Value: 0.036000000000000004
    }, {
      Category: "Amortization",
      Year: 2018,
      Value: 0.039
    }, {
      Category: "Amortization",
      Year: 2019,
      Value: 0.0408
    }, {
      Category: "Amortization",
      Year: 2020,
      Value: 0.044800000000000006
    }, {
      Category: "Amortization",
      Year: 2021,
      Value: 0.039599999999999996
    }, {
      Category: "Amortization",
      Year: 2022,
      Value: 0.0437
    }, {
      Category: "Fee",
      Year: 2012,
      Value: 0.26
    }, {
      Category: "Fee",
      Year: 2013,
      Value: 0.252
    }, {
      Category: "Fee",
      Year: 2014,
      Value: 0.285
    }, {
      Category: "Fee",
      Year: 2015,
      Value: 0.287
    }, {
      Category: "Fee",
      Year: 2016,
      Value: 0.43700000000000006
    }, {
      Category: "Fee",
      Year: 2017,
      Value: 0.415
    }, {
      Category: "Fee",
      Year: 2018,
      Value: 0.406
    }, {
      Category: "Fee",
      Year: 2019,
      Value: 0.4205
    }, {
      Category: "Fee",
      Year: 2020,
      Value: 0.3736
    }, {
      Category: "Fee",
      Year: 2021,
      Value: 0.3997
    }, {
      Category: "Fee",
      Year: 2022,
      Value: 0.39990000000000003
    }, {
      Category: "Interest",
      Year: 2012,
      Value: 0.207
    }, {
      Category: "Interest",
      Year: 2013,
      Value: 0.20800000000000002
    }, {
      Category: "Interest",
      Year: 2014,
      Value: 0.19699999999999998
    }, {
      Category: "Interest",
      Year: 2015,
      Value: 0.22399999999999998
    }, {
      Category: "Interest",
      Year: 2016,
      Value: 0.22949999999999998
    }, {
      Category: "Interest",
      Year: 2017,
      Value: 0.2289
    }, {
      Category: "Interest",
      Year: 2018,
      Value: 0.213
    }, {
      Category: "Interest",
      Year: 2019,
      Value: 0.19440000000000002
    }, {
      Category: "Interest",
      Year: 2020,
      Value: 0.1867
    }, {
      Category: "Interest",
      Year: 2021,
      Value: 0.1623
    }, {
      Category: "Interest",
      Year: 2022,
      Value: 0.174
    }];
    ////////////////////////////////////////////////////////////
    //////////////////////// 0 USER SELECTION //////////////////
    ////////////////////////////////////////////////////////////
    const isYGridLine = "n";
    const isXGridLine = "n";
    const xAxisTickDefault = "9";
    const yAxisTickDefault = "-9";
    ////////////////////////////////////////////////////////////
    //////////////////////// 1 DATA WRANGLING //////////////////
    ////////////////////////////////////////////////////////////
    const xAccessor = (d) => d.Year;
    const yAccessor = (d) => d.Value;
    const zAccessor = (d) => d.Category;
    //get all the unique categories
    const cat = [...new Set(data.map(zAccessor))];
    const year = [...new Set(data.map(xAccessor))];
    const count = year.length - 1;
    const maxYear = Math.max(...year);
    console.log(cat);
    //const yr1 = [1951, 1961, 1971, 1981, 1991, 2001];
    const src2 = cat.map((d, i) => {
      return {
        cat: d,
        year: null,
        val: null
      };
    });
    ////////////////////////////////////////////////////////////
    //////////////////////// 2 CREATE SVG //////////////////////
    ////////////////////////////////////////////////////////////
    //namespace
    //define dimension
    const width = 1536;
    const height = 720;
    const svgns = "http://www.w3.org/2000/svg";
    const svg = d3.select("svg");
    svg.attr("xmlns", svgns).attr("viewBox", `0 0 ${width} ${height}`);
    svg.append("rect").attr("class", "vBoxRect")
      //.style("overflow", "visible")
      .attr("width", `${width}`).attr("height", `${height}`).attr("stroke", "black").attr("fill", "white");
    ////////////////////////////////////////////////////////////
    //////////////////////// 3 CREATE BOUND ////////////////////
    ////////////////////////////////////////////////////////////
    const padding = {
      top: 70,
      bottom: 100,
      left: 120,
      right: 120
    };
    const multiplierH = 1; //controls the height of the visual container
    const multiplierW = 1; //controls the width of the visual container
    const boundHeight = height * multiplierH - padding.top - padding.bottom;
    const boundWidth = width * multiplierW - padding.right - padding.left;
    //create BOUND rect -- to be deleted later
    svg.append("rect").attr("class", "boundRect").attr("x", `${padding.left}`).attr("y", `${padding.top}`).attr("width", `${boundWidth}`).attr("height", `${boundHeight}`).attr("fill", "white");
    //create bound element
    const bound = svg.append("g").attr("class", "bound")
      //specify transform, must be .style and not .attr, px needs to be mentioned
      .style("transform", `translate(${padding.left}px,${padding.top}px)`);
    ////////////////////////////////////////////////////////////
    //////////////////////// 4 CREATE SCALE ////////////////////
    ////////////////////////////////////////////////////////////
    //scale converts a domain (data) to range (pixel)
    const scaleX = d3.scaleLinear().range([0, boundWidth]).domain(d3.extent(data, xAccessor));
    const scaleY = d3.scaleLinear().range([boundHeight, 0]).domain(d3.extent(data, yAccessor));
    ////////////////////////////////////////////////////////////
    //////////////////////// 5 CREATE AXIS/ ////////////////////
    ////////////////////////////////////////////////////////////
    //X Axis
    bound.append("g").attr("class", "xAxis").append("g").attr("class", "xAxisBottom").call(d3.axisBottom(scaleX).ticks(count) //forces all the years on the x axis
      .tickSizeOuter(0) //removes ticksize
      .tickFormat(d3.format("d")) //tick format integer
    ).call((d) => isXGridLine == "n" ? d3.selectAll(".xAxisBottom>.tick>line").remove() : null).style("transform", `translateY(${boundHeight}px)`);
    //formatting parent, remove unnecessary property from there
    d3.select(".xAxisBottom").attr("font-size", null).attr("font-family", null).attr("text-anchor", null);
    d3.select(".xAxisBottom>path").attr("stroke", "black").style("stroke-width", "2")
      //.style('opacity', '.2')
      .style("stroke-miterlimit", "10");
    d3.selectAll(".xAxisBottom>.tick>text").style("text-anchor", function(d, i) {
      return i === 0 ? "start" : i === count ? "end" : "middle";
    }).attr("y", `${xAxisTickDefault}`);
    //////////////////////// 5a X AXIS ANIMATION/ ////////////////////
    /*animate X Axis - global*/
    const duration1 = 10000;
    /*Animate X AXIS*/
    d3.select(".xAxis>.xAxisBottom>.domain").transition().duration(`${duration1}`).ease(d3.easeQuadIn).attrTween("stroke-dasharray", function() {
      const selection = this;
      const length = selection.getTotalLength();
      const start = 0;
      const finish = 1;
      const interpolator = d3.interpolate(start, finish);
      return function(t) {
        const pct = interpolator(t);
        const val = selection.getPointAtLength(length * pct);
        const valX = val.x;
        const invertVal = scaleX.invert(valX);
        return `${valX} ${length}`;
      };
    });
    // construct div
    d3.select("body").append("div").classed("exp", true).selectAll("div").data(src2).join("div").attr("class", (d, i) => {
      return d.cat;
    }).transition().duration(`${duration1}`).textTween(function(d, i) {
      const selection = d3.select(".xAxis>.xAxisBottom>.domain");
      const node = selection.node();
      const length = node.getTotalLength();
      const start = 0;
      const finish = 1;
      const interpolator = d3.interpolate(start, finish);
      return function(t) {
        const pct = interpolator(t);
        const val = node.getPointAtLength(length * pct);
        const valX = val.x;
        const invertVal = scaleX.invert(valX);
        //src2.map((a, i) => (a.year = invertVal));
        //console.log(src2);
        return `${d.cat}` + ":-" + invertVal;
        //console.log(d);
      };
    });
  </script>
</html> ```

The immediate reason is that d3.select('body') doesn’t refer to the body in your html cell, rather it refers to the document’s body - whatever that is.

More generally, you should absolutely avoid things like d3.select('#id') - that’s called an anti-pattern in Observable. You’re doing two anti-patterns

Both of those are inefficient, error prone, and make your code more difficult to follow.