d3 v5 plotting simple line chart and mapping/casting to numbers using promise

女生的网名这么多〃 提交于 2020-07-22 22:15:47

问题


I have finally decided to saddle up and adopt d3 v5 syntax after years of using v3. After looking at some tutorials and examples, v5 syntax really struck me as sublime. The readability is far improved and it seems easier to integrate multiple data sources.

To my dismay, and despite my reverence of it, I couldn't quite build a visual from scratch with the new Promise syntax. Here is my simple graph: (note I'm using hard coded data for the sake of this post, and I have commented out the .csv() call that I'd actually use. It should still be functionally the same)

var margins = {top:50, right:50, bottom:50, left:50};

var width = window.innerWidth - margins.left - margins.right;
var height = window.innerHeight - margins.top - margins.bottom;

var sampleData = [
  {'y':32, 'x':1},
  {'y':20, 'x':2},
  {'y':19, 'x':3},
  {'y':12, 'x':4},
  {'y':15, 'x':5},
  {'y':19, 'x':6},
  {'y':22, 'x':7},
  {'y':26, 'x':8},
  {'y':31, 'x':9},
  {'y':36, 'x':10}
];


//var dataset = d3.csv("my-data.csv").then(function(data)
//  {return data;
//  });

var dataset = sampleData.then(function(data)
  {return data;
  });

var svg = d3.select('body').append('svg').attr('id','svg').attr('height','100%').attr('width','100%');

var myLine = dataset.then(function(data) {
  Promise.all(data.map(function(d) {return {X:+d.x, Y:+d.y}}))//ensure numeric parsing

  var xScale = d3.scaleLinear()
      .domain(d3.extent(data, function(d) { return d.X; }))
      .range([0,width]);

  var yScale = d3.scaleLinear()
      .domain(d3.extent(data, function(d) {return d.Y; }))
      .range([height,0]);

  var xAxis = d3.axisBottom(xScale);

  var yAxis = d3.axisLeft(yScale);

  var line = d3.line()
      .x(function(d) {return xScale(d.x); })
      .y(function(d) {return yScale(d.y); });

  var svg = d3.select('body').append('svg').attr('id','svg').attr('height','100%').attr('width','100%');

  var graphGroup = svg.append('g')
      .attr('transform',"translate("+margins.left+","+margins.top+")");

  graphGroup.append('path')
      .attr('d', function(d) {return line(data); });

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

  graphgroup.append('g')
      .attr('class', 'axis y')
      .call(yAxis);


    });

I get this error in the console:

Uncaught TypeError: sampleData.then is not a function

Question

I take the point that Promise.all() and .then() are not always favorable for really simple data visuals, but I'd still like to know why I can't make the above script output a minimal line graph. From then, hopefully, I can slowly take the training wheels off and find my stride with v5.

I'm particularly confused with how to cast to numbers using the unary + with Promise.


回答1:


Although there are many twists and turns when it comes to using Promises, it turns out that the actual changes required to port code to make use of the d3-fetch module in favor of the deprecated d3-request module are strikingly minimal. Loosely speaking, to adapt your—or any pre-v5—code to use the new d3-fetch module you just move the callback from one method to another. Thus, the former

d3.dsv(url, callback);

now becomes

d3.dsv(url).then(callback);

The only thing to be aware of is to check if the callback's signature matches the one expected for .then. This only becomes relevant, though, if your callback used two parameters to handle errors:

function callback(error, data) {
  // Handle error
  if (error) throw error;

  // Manipulate data
}

With Promises this is split into two separated methods:

function onFullfilled(data) {
  // Manipulate data
}

function onRejected(error) {
  // Handle error
}

These callback can be used in two ways:

// 1.
d3.dsv(url).then(onFullfilled, onRejected);

// 2.
d3.dsv(url).then(onFullfilled).catch(onRejected);

Another important point is that you cannot return data from your callback (beware of the infamous "How do I return the response from an asynchronous call?"!). d3.dsv now returns a Promise, not your data; you have to handle the data inside your callback. If you become more skilled using Promises you might have a look into the await operator, though, which allows you to wait for a Promise and its fulfilled value. Although this is ECMAScript 2017 (ES8) syntax it has already seen wide-spread browser support.


That being the general case, now for your code: sampleData is an Array object which, of course, does not have a .then() method and, hence, the error. To make the code work there is not much to do apart from uncommenting the lines featuring d3.dsv and putting the relevant code handling data inside the callback.

If you really want to do an offline simulation with hardcoded data you can use Promise.resolve() which will return a Promise already resolved with the given value. In your case instead of

d3.csv("my-data.csv")
  .then(function(data) { });

you can use

Promise.resolve(sampleDate)
  .then(function(data) { });   // Same handler as above


来源:https://stackoverflow.com/questions/53700460/d3-v5-plotting-simple-line-chart-and-mapping-casting-to-numbers-using-promise

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