Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
206 views
in Technique[技术] by (71.8m points)

javascript - D3.js Line chart not updating properly when applying the general update pattern

I have a project where I try to create a line chart. I have used the general update pattern in order to properly load, update and delete lines on the chart. I have already used this pattern on other charts types(barchart, scatterplot).

But I cannot get the general update pattern to work on my line chart. I have added the three states the line can be in when updating the data: enter, update and exit.

In the enter state the line should transition in from the bottom of the chart. This seems to be working perfectly fine.

The main problem is when I try to update the chart. When the chart switches to a new set of data, each point on the line should transition to its new position. But in my code does not seem to trigger the update part of the code. The line comes from the bottom of the screen instead of transitioning from the previous state.

This is the section of my code where I try to implement the general update pattern on a line chart:

      // Update line.
      this.line = this.svg.selectAll(".line").data([data], d => d.key)
      this.line = this.line
        .data([data], d => d.key)
        .join(
          enter => {
            enter
              .append("path")
              .attr("class", "line")
              .merge(this.line)
              .attr("fill", "none")
              .attr("stroke", "#206BF3")
              .attr("stroke-width", 4)
              .attr(
                "d",
                d3
                .line()
                .x(d => {
                  return this.xScale(d.key);
                })
                .y(() => {
                  return this.yScale(0);
                })
              )
              .transition(t)
              .attr(
                "d",
                d3
                .line()
                .x(d => {
                  return this.xScale(d.key);
                })
                .y(d => {
                  return this.yScale(d.value);
                })
              );
          },

          update => {
            update.transition(t).attr(
              "d",
              d3
              .line()
              .x(d => {
                return this.xScale(d.key);
              })
              .y(d => {
                return this.yScale(d.value);
              })
            );
          },

          exit => exit.remove()
        );

This is a working snippet of the state my chart is in now:

new Vue({
  el: "#app",
  data() {
    return {
      index: 0,
      data: [
        [{
            key: "Jan",
            value: 5787
          },
          {
            key: "Feb",
            value: 6387
          },
          {
            key: "Mrt",
            value: 7375
          },
          {
            key: "Apr",
            value: 6220
          },
          {
            key: "Mei",
            value: 6214
          },
          {
            key: "Jun",
            value: 5205
          },
          {
            key: "Jul",
            value: 5025
          },
          {
            key: "Aug",
            value: 4267
          },
          {
            key: "Sep",
            value: 6901
          },
          {
            key: "Okt",
            value: 5800
          },
          {
            key: "Nov",
            value: 7414
          },
          {
            key: "Dec",
            value: 6547
          }
        ],
        [{
            key: "Jan",
            value: 2562
          },
          {
            key: "Feb",
            value: 3882
          },
          {
            key: "Mrt",
            value: 2323
          },
          {
            key: "Apr",
            value: 1283
          },
          {
            key: "Mei",
            value: 3526
          },
          {
            key: "Jun",
            value: 4578
          },
          {
            key: "Jul",
            value: 3848
          },
          {
            key: "Aug",
            value: 3564
          },
          {
            key: "Sep",
            value: 2436
          },
          {
            key: "Okt",
            value: 2536
          },
          {
            key: "Nov",
            value: 2733
          },
          {
            key: "Dec",
            value: 3172
          }
        ]
      ]
    }
  },
  mounted() {
    // set the dimensions and margins of the graph
    var margin = {
        top: 20,
        right: 20,
        bottom: 30,
        left: 30
      },
      width = 500 - margin.left - margin.right;

    this.height = 200 - margin.top - margin.bottom;

    // append the svg obgect to the body of the page
    // appends a 'group' element to 'svg'
    // moves the 'group' element to the top left margin
    this.svg = d3
      .select("#my_dataviz")
      .append("svg")
      .attr(
        "viewBox",
        `0 0 ${width + margin.left + margin.right} ${this.height +
          margin.top +
          margin.bottom}`
      )
      .attr("preserveAspectRatio", "xMinYMin")
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // set the ranges
    this.xScale = d3
      .scalePoint()
      .range([0, width])
      .domain(
        this.data.map(function(d) {
          return d.key;
        })
      )
      .padding(0.5);

    this.yScale = d3.scaleLinear().rangeRound([this.height, 0]);

    this.yScale.domain([0, 7000]);

    // Draw Axis
    this.xAxis = d3.axisBottom(this.xScale);

    this.xAxisDraw = this.svg
      .append("g")
      .attr("class", "x axis")
      .attr("transform", `translate(0, ${this.height})`);

    this.yAxis = d3
      .axisLeft(this.yScale)
      .tickValues([0, 7000])
      .tickFormat(d => {
        if (d > 1000) {
          d = Math.round(d / 1000);
          d = d + "K";
        }
        return d;
      });

    this.yAxisDraw = this.svg.append("g").attr("class", "y axis");

    this.update(this.data[this.index]);
  },
  methods: {
    swapData() {
      if (this.index === 0) this.index = 1;
      else this.index = 0;
      this.update(this.data[this.index]);
    },
    update(data) {
      // Update scales.
      this.xScale.domain(data.map(d => d.key));
      this.yScale.domain([0, 7000]);

      // Set up transition.
      const dur = 1000;
      const t = d3.transition().duration(dur);

      // Update line.
      this.line = this.svg.selectAll(".line").data([data], d => d.key)
      this.line = this.line
        .data([data], d => d.key)
        .join(
          enter => {
            enter
              .append("path")
              .attr("class", "line")
              .merge(this.line)
              .attr("fill", "none")
              .attr("stroke", "#206BF3")
              .attr("stroke-width", 4)
              .attr(
                "d",
                d3
                .line()
                .x(d => {
                  return this.xScale(d.key);
                })
                .y(() => {
                  return this.yScale(0);
                })
              )
              .transition(t)
              .attr(
                "d",
                d3
                .line()
                .x(d => {
                  return this.xScale(d.key);
                })
                .y(d => {
                  return this.yScale(d.value);
                })
              );
          },

          update => {
            update.transition(t).attr(
              "d",
              d3
              .line()
              .x(d => {
                return this.xScale(d.key);
              })
              .y(d => {
                return this.yScale(d.value);
              })
            );
          },

          exit => exit.remove()
        );

      // Update Axes.
      this.yAxis.tickValues([0, 7000]);
      if (data.length > 12) {
        this.xAxis.tickValues(
          data.map((d, i) => {
            if (i % 3 === 0) return d.key;
            else return 0;
          })
        );
      } else {
        this.xAxis.tickValues(
          data.map(d => {
            return d.key;
          })
        );
      }
      this.yAxis.tickValues([0, 7000]);
      this.xAxisDraw.transition(t).call(this.xAxis.scale(this.xScale));
      this.yAxisDraw.transition(t).call(this.yAxis.scale(this.yScale));
    }
  }
})
<div id="app">
  <button @click="swapData">Swap</button>
  <div id="my_dataviz" class="flex justify-center"></div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://d3js.org/d3.v6.js"></script>
question from:https://stackoverflow.com/questions/66060548/d3-js-line-chart-not-updating-properly-when-applying-the-general-update-pattern

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

only need to remove the merge from the update section.

The join is doing a merge automatically. When you add the merge in the update you are in fact merging the enter part with the update. So when you run the update, there is nothing left to do

new Vue({
  el: "#app",
  data() {
    return {
      index: 0,
      data: [
        [{
            key: "Jan",
            value: 5787
          },
          {
            key: "Feb",
            value: 6387
          },
          {
            key: "Mrt",
            value: 7375
          },
          {
            key: "Apr",
            value: 6220
          },
          {
            key: "Mei",
            value: 6214
          },
          {
            key: "Jun",
            value: 5205
          },
          {
            key: "Jul",
            value: 5025
          },
          {
            key: "Aug",
            value: 4267
          },
          {
            key: "Sep",
            value: 6901
          },
          {
            key: "Okt",
            value: 5800
          },
          {
            key: "Nov",
            value: 7414
          },
          {
            key: "Dec",
            value: 6547
          }
        ],
        [{
            key: "Jan",
            value: 2562
          },
          {
            key: "Feb",
            value: 3882
          },
          {
            key: "Mrt",
            value: 2323
          },
          {
            key: "Apr",
            value: 1283
          },
          {
            key: "Mei",
            value: 3526
          },
          {
            key: "Jun",
            value: 4578
          },
          {
            key: "Jul",
            value: 3848
          },
          {
            key: "Aug",
            value: 3564
          },
          {
            key: "Sep",
            value: 2436
          },
          {
            key: "Okt",
            value: 2536
          },
          {
            key: "Nov",
            value: 2733
          },
          {
            key: "Dec",
            value: 3172
          }
        ]
      ]
    }
  },
  mounted() {
    // set the dimensions and margins of the graph
    var margin = {
        top: 20,
        right: 20,
        bottom: 30,
        left: 30
      },
      width = 500 - margin.left - margin.right;

    this.height = 200 - margin.top - margin.bottom;

    // append the svg obgect to the body of the page
    // appends a 'group' element to 'svg'
    // moves the 'group' element to the top left margin
    this.svg = d3
      .select("#my_dataviz")
      .append("svg")
      .attr(
        "viewBox",
        `0 0 ${width + margin.left + margin.right} ${this.height +
          margin.top +
          margin.bottom}`
      )
      .attr("preserveAspectRatio", "xMinYMin")
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // set the ranges
    this.xScale = d3
      .scalePoint()
      .range([0, width])
      .domain(
        this.data.map(function(d) {
          return d.key;
        })
      )
      .padding(0.5);

    this.yScale = d3.scaleLinear().rangeRound([this.height, 0]);

    this.yScale.domain([0, 7000]);

    // Draw Axis
    this.xAxis = d3.axisBottom(this.xScale);

    this.xAxisDraw = this.svg
      .append("g")
      .attr("class", "x axis")
      .attr("transform", `translate(0, ${this.height})`);

    this.yAxis = d3
      .axisLeft(this.yScale)
      .tickValues([0, 7000])
      .tickFormat(d => {
        if (d > 1000) {
          d = Math.round(d / 1000);
          d = d + "K";
        }
        return d;
      });

    this.yAxisDraw = this.svg.append("g").attr("class", "y axis");

    this.update(this.data[this.index]);
  },
  methods: {
    swapData() {
      if (this.index === 0) this.index = 1;
      else this.index = 0;
      this.update(this.data[this.index]);
    },
    update(data) {
      // Update scales.
      this.xScale.domain(data.map(d => d.key));
      this.yScale.domain([0, 7000]);

      // Set up transition.
      const dur = 1000;
      const t = d3.transition().duration(dur);

      // Update line.
      this.line = this.svg.selectAll(".line").data([data], d => d.key)
      this.line = this.line
        .data([data], d => d.key)
        .join(
          enter => {
            enter
              .append("path")
              .attr("class", "line")
              .attr("fill", "none")
              .attr("stroke", "#206BF3")
              .attr("stroke-width", 4)
              .attr(
                "d",
                d3
                .line()
                .x(d => {
                  return this.xScale(d.key);
                })
                .y(() => {
                  return this.yScale(0);
                })
              )
              .transition(t)
              .attr(
                "d",
                d3
                .line()
                .x(d => {
                  return this.xScale(d.key);
                })
                .y(d => {
                  return this.yScale(d.value);
                })
              );
          },

          update => {
            update.transition(t).attr(
              "d",
              d3
              .line()
              .x(d => {
                return this.xScale(d.key);
              })
              .y(d => {
                return this.yScale(d.value);
              })
            );
          },

          exit => exit.remove()
        );

      // Update Axes.
      this.yAxis.tickValues([0, 7000]);
      if (data.length > 12) {
        this.xAxis.tickValues(
          data.map((d, i) => {
            if (i % 3 === 0) return d.key;
            else return 0;
          })
        );
      } else {
        this.xAxis.tickValues(
          data.map(d => {
            return d.key;
          })
        );
      }
      this.yAxis.tickValues([0, 7000]);
      this.xAxisDraw.transition(t).call(this.xAxis.scale(this.xScale));
      this.yAxisDraw.transition(t).call(this.yAxis.scale(this.yScale));
    }
  }
})
<div id="app">
  <button @click="swapData">Swap</button>
  <div id="my_dataviz" class="flex justify-center"></div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://d3js.org/d3.v6.js"></script>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...