问题
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