charlie090
charlie090

Reputation: 368

d3.json asynchronous solution

I have a variable nest created from d3.json:

d3.json("api_all.php", function(data) {
  data.forEach(e => {
    e.date = parseDate(e.date);
    e.value = +e.close;
    e.stockName = e.stock_name;
  });

  var nest = d3.nest()
      .key(function(d){
        return d.stockName;
      })
      .entries(data);

However I have another function down the code that needs to use the saved nest data:

var initialgraph = function(stockName) {
  /*Filter the data to include only stock of interest*/
  var selectStock = nest.filter(function(d) {
    return d.key == stockName;
  })

  var selectStockGroups = svg.selectAll(".stockGroups")
      .data(selectStock, function(d) {
        return d ? d.key : this.key;
      })
      .enter()
      .append("g")
      .attr("class", "stockGroups")
      .each(function(d) {
        y.domain([0, d3.max(data, function(d) { return d.value; })])
      });

  var initialPath = selectStockGroups.selectAll(".rect")
      .data(function(d) {return d.value.year})
      .enter()
      .append("path")

  initialPath
    .attr("d", function(d) {
      return valueLine(d.values)
    })
    .attr("class", "rect")

    /*Add Y Axis*/
    var yaxis = svg.append("g")
        .attr("class", "y axis")
        .call(d3.axisLeft(y))
};

I have yet to find a solution for this problem...I was thinking 1) making the nest variable a global variable or 2) to include the initialgraph function as part of the d3.json data pull.

I have yet to find the right way to do #1 and #2 seems to cause a lot of additional problems for my code. So my bias is just finding a solution to doing #1. What are you suggestions? Thanks.

Upvotes: 0

Views: 1487

Answers (1)

rioV8
rioV8

Reputation: 28673

Call the initialgraph with the nest result as an argument. You only have a valid value in the nest variable when the callback is executing.

var initialgraph = function (stockName, nest) {
  // Filter the data to include only stock of interest
  var selectStock = nest.filter(function(d) { return d.key == stockName; });
  //....
};

d3.json("api_all.php", function(data) {
  data.forEach(e => {
    e.date = parseDate(e.date);
    e.value = +e.close;
    e.stockName = e.stock_name;
  });

  var nest = d3.nest()
      .key(function (d) { return d.stockName; })
      .entries(data);

  var xExtent = d3.extent(data, function(d) { return d.date; });
  x.domain(xExtent);
  zoom.translateExtent([[x(xExtent[0]), -Infinity], [x(xExtent[1]), Infinity]])
  y.domain([0, d3.max(data, function(d) { return d.value; })]);
  yGroup.call(yAxis).select(".domain").remove();
  areaPath.datum(data);
  zoomRect.call(zoom.transform, d3.zoomIdentity);

  /*Build Dropdown Menu*/
  var stockDropDown = d3.select("#dropdown")
      stockDropDown
      .append("select")
      .selectAll("option")
        .data(nest)
        .enter()
        .append("option")
        .attr("value", function(d){
          return d.key;
        })
        .text(function(d){
          return d.key;
        });
  initialgraph("KYOKUYO CO.,LTD.", nest);

});

If initialgraph is defined inside the callback you have no problem because it is a closure.

Edit

Like answered today about async functions, when d3.json() returns does not mean the call back is called/finished. Remove the initialgraph() call from line 134. And call it at the end of the callback.

Edit 2

Needed to look up an example for d3v4 and json to see what the interface is of the d3.json(). According to the docs it is using fetch and promises but apparently not. The real interface is

d3.json(url, function (error, data) {
    // process the data
});

Here is the complete code that shows at least the initial graph based on the json. I have not verified if it draws the correct graph.

Calling y.domain() in an each() call does not make sense. The max value is already determined based on the json data. Maybe it is an idea to use the extend of the d.value: d3.extend(data, d=>d.value);

The updategraph() now gets a valid nest value.

var svg = d3.select("svg"),
    margin = {top: 20, right: 20, bottom: 30, left: 60},
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom,
    g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

/*Define the rect*/


/*Parse dates*/
var parseDate = d3.timeParse("%Y-%m-%d"),
    formatDate = d3.timeFormat("%Y");

/*Set the ranges*/
var x = d3.scaleTime()
    .domain([new Date(2002, 0, 1), new Date(2003, 0, 1)])
    .range([0, width]);
var y = d3.scaleLinear()
    .range([height, 0]);

/*Create axes*/
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);

var area = d3.area()
    .curve(d3.curveStepAfter)
    .y0(y(0))
    .y1(function(d) { return y(d.value); });

var areaPath = g.append("path")
    .attr("clip-path", "url(#clip)")
    .attr("fill", "steelblue");

var yGroup = g.append("g");

var xGroup = g.append("g")
    .attr("transform", "translate(0," + height + ")");

var zoom = d3.zoom()
    .scaleExtent([1 / 4, 8])
    .translateExtent([[-width, -Infinity], [2 * width, Infinity]])
    .on("zoom", zoomed);

var zoomRect = svg.append("rect")
    .attr("width", width)
    .attr("height", height)
    .attr("fill", "none")
    .attr("pointer-events", "all")
    .call(zoom);

g.append("clipPath")
    .attr("id", "clip")
    .append("rect")
    .attr("width", width)
    .attr("height", height);

/*Import Data from API*/
d3.json("api_all.php", function(error, data) {
  data.forEach(e => {
    e.date = parseDate(e.date);
    e.value = +e.close;
    e.stockName = e.stock_name;
  });

  var nest = d3.nest()
      .key(function(d){
        return d.stockName;
      })
      .entries(data);

/*Scale range of the data*/
  var xExtent = d3.extent(data, function(d) { return d.date; });
  x.domain(xExtent);
  zoom.translateExtent([[x(xExtent[0]), -Infinity], [x(xExtent[1]), Infinity]])
  y.domain([0, d3.max(data, function(d) { return d.value; })]);
  yGroup.call(yAxis).select(".domain").remove();
  areaPath.datum(data);
  zoomRect.call(zoom.transform, d3.zoomIdentity);

/*Build Dropdown Menu*/
  var stockDropDown = d3.select("#dropdown");
  stockDropDown
      .append("select")
      .selectAll("option")
        .data(nest)
        .enter()
        .append("option")
        .attr("value", function(d){
          return d.key;
        })
        .text(function(d){
          return d.key;
        });
  stockDropDown.on("change", function() {
  var selectedStock = d3.select(this)
      .select("select")
      .property("value");
      updateGraph(selectedStock, nest);
  });

  initialgraph("KYOKUYO CO.,LTD.", nest);
});

/*Function to create initial graph*/
var initialgraph = function(stockName, nest) {
  /*Filter the data to include only stock of interest*/
  var selectStock = nest.filter(function(d) {
    return d.key == stockName;
  })


  var selectStockGroups = svg.selectAll(".stockGroups")
      .data(selectStock, function(d) {
        return d ? d.key : this.key;
      })
      .enter()
      .append("g")
      .attr("class", "stockGroups")
    //   .each(function(d) {
    //          y.domain([0, d3.max(data, function(d) { return d.value; })]);
    //   })
      ;

  var initialPath = selectStockGroups.selectAll(".rect")
      .data(function(d) {return d.value.year})
      .enter()
      .append("path")

  initialPath
    .attr("d", function(d) {
      return valueLine(d.values)
    })
    .attr("class", "rect")

    /*Add Y Axis*/
    var yaxis = svg.append("g")
        .attr("class", "y axis")
        .call(d3.axisLeft(y))
};

/*Create initial graph*/
//initialgraph("KYOKUYO CO.,LTD.");

/*Update the data*/
var updateGraph = function(stockName, nest) {
  var selectStock  = nest.filter(function(d) {
    return d.key == stockName;
  })
  var selectStockGroups = svg.selectAll(".stockGroups")
      .data(selectStock)
      .each(function(d) {
        y.domain([0, d3.max(data, function(d) { return d.value; })])
      });
      selectStockGroups.selectAll("path.rect")
        .data(function(d) {return d.value.year;}, function(d) {return d.key})
        .transition()
          .duration(1000)
          .attr("d", function(d) {
            return valueLine(d.values)
          })
}

/*Zoom function*/
function zoomed() {
  var xz = d3.event.transform.rescaleX(x);
  xGroup.call(xAxis.scale(xz));
  areaPath.attr("d", area.x(function(d) { return xz(d.date); }));
}

Upvotes: 1

Related Questions