我正在使用d3.js加载2个csv文件,并希望将它们合并。然而,我被困在一些更基本的东西上。
我有以下功能可以正常工作:
function loadData(file) {
d3.csv(file, function (d){...},
function (data) {displayData(data);});
}
现在我尝试以loadData()
方式重构data
对象来重构代码,因此我可以调用它两次,合并data
数组并调用{{1}与合并的数组。
我尝试返回displayData()
:
data
使用全局变量
function loadData(file) {
d3.csv(file, function (d){...},
function (data) {return data});
return data;
}
在许多其他事情中,似乎没有任何效果。
令人惊讶的是,
使用全局变量
var gdata1 = {};
var gdata2 = {};
function loadData(file) {
d3.csv(file, function (d){...},
function (data) {gdata = data});
gdata2 = data;
}
工作正常。
任何人都可以解释一下将我的数据数据从displayData函数中取出的最佳/正确方法是什么以及如何合并两个数据数组(我希望数据是一个映射数组,例如data [0]是一个映射)
答案 0 :(得分:2)
Promises可以帮助您处理一些对回调有点不愉快的事情。你应该看看它们。
这个小小的d3插件可以让它工作:https://github.com/kristw/d3.promise
Promise.all([
d3.promise.csv(url1, formatter),
d3.promise.csv(url2, formatter)
]).then(function(results){
console.log('all files have been loaded');
var formatedDataFromUrl1 = results[0];
var formatedDataFromUrl2 = results[1];
//proceed/combine/render the results as you wish
});
所以基本上d3.promise.csv
会替换你的loadData
功能。
或者你将它包装成如下以便始终使用相同的格式化程序:
function loadData(file) {
return d3.promise.csv(file, function (d){...});
}
修改强>
不幸的是我不能使用任何插件,只能使用“核心”d3
然后你基本上可以将整个插件复制粘贴到你的代码中,它真的没那么多;)
对于这种特殊情况,核心功能可归结为:
function loadCsv(url){
return new Promise(function(resolve, reject){
d3.csv(url, function (d){...}, function(err, data){
if(err) reject(Error(err));
else resolve(data);
});
});
}
该插件几乎只是以相同的方式包含了一些(如json,xml,...)的方法,因此更加通用。你应该看一下源代码。
答案 1 :(得分:2)
loadData()
应该接听回电。然后,您可以在第一个文件的回调中加载第二个文件。
function loadData(file, callback) {
d3.csv(file, function(d) { ...}, callback);
}
loadData(file1, function(err1, data1) {
loadData(file2, function(err2, data2) {
// code to combine data1 and data2 and display result
});
});
这样做的缺点在于它序列化了文件访问,因此它不如使用Promise.all()
的承诺那样高效,就像托马斯的回答一样。
要处理任意数量的文件,您可以使用每次递增的变量从数组中提取它们。
function loadNextFile(files, i, dataArray) {
if (i >= files.length) {
// merge dataArray and display it
} else {
loadData(files[i], function(err, data) {
dataArray.push(data);
loadNextFile(files, i+1, dataArray);
}
}
}
var filesToLoad = [...];
loadNextFile(filesToLoad, 0, []);
答案 2 :(得分:2)
管理多个并发请求的状态,然后同步结果可能相当有用。
管理状态是Promises的主要目的之一,Promise.all正在同步和合并结果。
这也是以下代码的主要目的。还有两件事要说:
此代码未经过测试,可能包含一些错误
我已经对此代码中的所有内容进行了评论四,了解它的目的是什么/机制是什么,它有什么能力,以及如何处理这个怪物的不同用例。这就是为什么这个答案最终结束的原因。
由于加载单个文件的实际代码太短且孤立,我决定将其放入外部函数中,这样您只需传递一个不同的实用程序函数来执行实际请求即可重用整个代码。
因为我喜欢使用索引访问的普通数组的命名映射(它更容易混淆名称而不是索引),我也将这种可能性集成在一起。如果您不确切地知道我的意思,请查看主函数之后的示例。
作为额外的糖,并且由于它仅进行了一些小调整,我已经使返回的函数递归,因此它可以处理几乎所有传递给它的URL作为“列表”。
function loadFilesFactory( loadFile ){
function isNull(value){ return value === null }
//takes an array of keys and an array of values and returns an object mapping keys to values.
function zip(keys, values){
return keys.reduce(function(acc, key, index){
acc[key] = values[index];
return acc;
}, Object.create(null)); //if possible
//}, {}); //if Object.create() doesn't work on the browser you need to support
}
//a recursive function that can take pretty much any composition as "url"
//and will "resolve" to a similar composition of results,
//while loading everything in paralell
//see the examples
var recursiveLoadFilesFunction = function(arg, callback){
if(arg !== Object(arg)){
//arg is a primitive
return loadFile(arg, callback);
}
if(!Array.isArray(arg)){
//arg is an object
var keys = Object.keys(arg);
return recursiveLoadFilesFunction(keys.map(function(key){
return arg[key];
}), function(error, values){
if(error) callback(error)
else callback(null, zip(keys, values));
});
}
//arg contains an array
var length = arg.length;
var pending = Array(length)
var values = Array(length);
//If there is no request-array anymore,
//then some (sync) request has already finished and thrown an error
//no need to proceed
for(var i = 0; pending && i<length; ++i){
//I'd prefer if I'd get the request-object to be able to abort this, in case I'd need to
pending[i] = recursiveLoadFilesFunction(
arg[i],
createCallbackFor(i)
//but if I don't get a sufficient value, I need at least to make sure that this is not null/undefined
) || true;
}
var createCallbackFor = function(index){
return function(error, data){
//I'm done, this shouldn't have been called anymore
if(!pending || pending[index] === null) return;
//this request is done, don't need this request-object anymore
pending[index] = null;
if(error){
//if there is an error, I'll terminate early
//the assumption is, that all these requests are needed
//to perform, whatever the reason was you've requested all these files.
abort();
values = null;
}else{
//save this result
values[index] = data;
}
if(error || pending.every( isNull )){
pending = null; //says "I'm done"
callback(err, values);
}
}
}
var abort = function(){
if(pending){
//abort all pending requests
pending.forEach(function(request){
if(request && typeof request.abort === "function")
request.abort();
});
//cleanup
pending = null;
}
}
return {
//providing the ability to abort this whole batch.
//either manually, or recursive
abort: abort
}
}
return recursiveLoadFilesFunction;
}
这是唯一可以改变的部分,如果您想重复使用这些内容,让我们说JSON文件,或者不同的csv格式,或者其他什么
var loadCsvFiles = loadFilesFactory(function(url, callback){
if(!url || typeof url !== "string"){
callback(JSON.stringify(url) + ' is no valid url');
return;
}
return d3.csv(url, function(d){ ... }, callback);
});
这段代码可以处理什么?
//plain urls, sure
loadCsvFiles('url', function(err, result){ ... })
//an array of urls, it's inital purpose
loadCsvFiles(['url1', 'url2', 'url3'], function(err, results){
console.log(results[0], results[1], results[2]);
});
//urls mapped by property names, I've already mentioned that I prefer that over array indices
loadCsvFiles({
foo: 'file1.csv',
bar: 'file2.csv'
}, function(err, results){
//where `results` resembles the structure of the passed mapping
console.log(results.foo, results.bar);
})
//and through the recursive implementation,
//pretty much every imaginable (non-circular) composition of the examples before
//that's where it gets really crazy/nice
loadCsvFiles({
//mapping a key to a single url (and therefoere result)
data: 'data.csv',
//or one key to an array of results
people: ['people1.csv', 'people2.csv'],
//or a key to a sub-structure
clients: {
jim: 'clients/jim.csv',
//no matter how many levels deep
joe: {
sr: 'clients/joe.sr.csv',
jr: 'clients/joe.jr.csv',
},
//again arrays
harry: [
'clients/harry.part1.csv',
'clients/harry.part2.csv',
//and nested arrays are also possible
[
'clients/harry.part3a.csv',
'clients/harry.part3b.csv'
]
]
},
//of course you can also add objects to Arrays
images: [
{
thumbs: 'thumbs1.csv',
full: 'full1.csv'
},
{
thumbs: 'thumbs2.csv',
full: 'full2.csv'
}
]
}, function(err, results){
//guess what you can access on the results object:
console.log(
results.data,
results.people[0],
results.people[1],
results.clients.jim,
results.clients.joe.sr,
results.clients.joe.jr,
results.clients.harry[0],
results.clients.harry[1],
results.clients.harry[2][0],
results.clients.harry[2][1],
results.images[0].thumbs,
results.images[0].full,
results.images[1].thumbs,
results.images[1].full
)
});
特别是对于csv文件的荒谬结构而言,这最后一个例子对你没有任何意义,但这不是重点。关键是,完全取决于您如何构建数据。只需将它传递给这个文件加载器,它就会处理它。
如果您希望一次支持多种文件格式,也可以通过简单的调整来实现:
var loadDifferentFiles = loadFilesFactory(function(url, callback){
if(!url || typeof url !== "string"){
callback(JSON.stringify(url) + ' is no valid url');
return;
}
if(url.endsWith('.csv')){
return d3.csv(url, callback);
}
if(url.endsWith('.json')){
return d3.json(url, callback);
}
//return d3.text(url, callback);
callback('unsupported filetype: ' + JSON.stringify(url));
});
或某事。像这样
var loadDifferentFiles = loadFilesFactory(function(value, callback){
if(typeof value !== "string"){
if(value.endsWith('.csv')){
return d3.csv(value, callback);
}
if(value.endsWith('.json')){
return d3.json(value, callback);
}
}
//but in this case, if I don't know how to handle a value
//instead of resolving to an error, just forwarding the passed value to the callback,
//implying that it probably wasn't meant for this code.
callback(null, value);
});
答案 3 :(得分:0)
谢谢大家。我最终以递归方式调用函数:
var files = ['file1', 'file2', ...]
var alldata = [];
function loadData(files) {
if(files.length == 0)
{
displayData('', alldata);
return;
}
d3.csv(files[0],
function(error, data) {
....
alldata = alldata.concat(data);
files.shift()
loadData(files);
});
}
我确信其他解决方案也有效。