Updating D3 chart resulting in duplicate charts

拥有回忆 提交于 2019-12-22 00:16:40

问题


I am having a hell of a time trying to update JSON data going to a D3 histogram after a button click.

While I solved the button click to update issue, the D3 javascript is now appending one chart per button click, resulting in duplicate charts instead of simply updating the data.

I understand that the click event is calling append() in the D3 code every time it is triggered, but how can I get around this so there's only one chart with updated data per click?

console.log('chart.js loaded');

$(document).ready(function() {

  var vimeoVideoId = $('p#vimeoVideoId').text();
  var api = 'http://localhost:3001/videos/' + vimeoVideoId + '/json';

  function initData() {
    $('#all-notes').click(function() {
      getData();
    });
  }

  function getData() {
    $.getJSON(api, draw);
  }

  function draw(json) {

    data = json;
    var duration = data.duration;

    var timeToSec = function(data) {
      notes = [];
      // convert min:sec to seconds
      for(i=0; i < data.notes.length; i++) {
        var min_sec = data.notes[i].timecode;
        var tt = min_sec.split(':');
        var seconds = tt[0]*60+tt[1]*1;
        notes.push(seconds);
      }
      return notes;
    };

    noteTimes = timeToSec(data);
    console.log(noteTimes);

    // Formatters for counts and times (converting numbers to Dates).
    var formatCount = d3.format(",.0f"),
        formatTime = d3.time.format("%H:%M"),
        formatMinutes = function(d) { return formatTime(new Date(2012, 0, 1, 0, d)); };

    var margin = {top: 10, right: 20, bottom: 30, left: 20},
        width = 550;
        height = 285;

    var x = d3.scale.linear()
        .domain([0, duration])
        // .domain([0, d3.max(noteTimes)])
        .range([0, width]);

    // Generate a histogram using twenty uniformly-spaced bins.
    var data = d3.layout.histogram()
        .bins(x.ticks(50))
        (noteTimes);

    var y = d3.scale.linear()
        .domain([0, d3.max(data, function(d) { return d.y; })])
        .range([height, 0]);

    var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom")
        .tickFormat(formatMinutes);

    var svg = d3.select("#chartSet").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
      .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var bar = svg.selectAll(".bar")
        .data(data)
      .enter().append("g")
        .attr("class", "bar")
        .attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });

    bar.append("rect")
        .attr("x", 1)
        .attr("width", x(data[0].dx) - 1)
        .attr("height", function(d) { return height - y(d.y); });

    bar.append("text")
        .attr("dy", ".75em")
        .attr("y", 6)
        .attr("x", x(data[0].dx) / 2)
        .attr("text-anchor", "middle")
        .text(function(d) { return formatCount(d.y); });

    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);
  }

  initData();

});

回答1:


To handle creation and updates, you will have to reorganize how the draw function is written.

function draw(json) {

   // Initialization (few wasted CPU cycles)
   // ...

   // Update hook
   var svg = d3.select("#chartSet").data([data]);

   // Enter hook
   var svgEnter = svg.enter();

   // Enter cycle
   svgEnter.append("svg")
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Update cycle
    svg
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom);

    // Exit cycle (not really needed here)
    svg.exit().remove();

    // Update hook
    var bar = svg.selectAll(".bar")
        .data(data)

    // Enter hook
    var barEnter = bar.enter();

    // Enter cycle
    var barG_Enter = barEnter
                        .append("g")
                        .attr("class", "bar")
    barG_Enter
        .append("rect")
        .attr("x", 1);

    barG_Enter
        .append("text")
        .attr("dy", ".75em")
        .attr("y", 6)
        .attr("text-anchor", "middle");

    // Update cycle
    bar.attr("transform", function(d) { 
        return "translate(" + x(d.x) + "," + y(d.y) + ")"; });

    bar.select("rect")
        .attr("width", x(data[0].dx) - 1)
        .attr("height", function(d) { return height - y(d.y); });

    bar.select("text")
        .attr("x", x(data[0].dx) / 2)
        .text(function(d) { return formatCount(d.y); });

    // Exit cycle
    bar.exit().remove();

    // Enter cycle
    svgEnter.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

    // Update cycle
    svg.select('g.x.axis')
        .call(xAxis);
}

This classical enter-update-exit pattern is improved upon in this article on making reusable graphs. This answer draws heavily on that pattern.

With a slightly better implementation which uses closures, you will be able to save the few CPU cycles wasted on initialization each time.




回答2:


You can either create the SVG outside the function that you call when the button is clicked, or check whether it exists and only add it if it does not exist. That is, something like

var svg = d3.select("svg");
if(svg.empty()) {
    // code for appending svg
}

The same thing applies to the block of code at the end of your function that appends the x axis. For the bars, all you need to do is handle the update and exit selections in addition to the enter selection, i.e. set the dimensions and potentially remove elements based on the new data.



来源:https://stackoverflow.com/questions/15773140/updating-d3-chart-resulting-in-duplicate-charts

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!