该输入(tree-like
结构)必须被格式化成特定格式来绘制一d3 sankey diagram chart
。
let unformattedJson = [
{
"key": "a1",
"value": 30,
"buckets": [
{
"key": "a2",
"value": 10
},
{
"key": "b2",
"value": 20
}
]
},
{
"key": "b1",
"value": 70,
"buckets": [
{
"key": "b2",
"value": 40
},
{
"key": "c2",
"value": 30
}
]
}
]
预计输出我需要生成是:
{
"nodes": [
{"nodeId":0,"name":"a1"},
{"nodeId":1,"name":"a2"},
{"nodeId":2,"name":"b2"},
{"nodeId":3,"name":"b1"},
{"nodeId":4,"name":"c2"}
],
"links": [
{"source":0,"target":1,"value":10},
{"source":0,"target":2,"value":20},
{"source":3,"target":2,"value":40},
{"source":3,"target":4,"value":30}
]
}
我对解决方案的方法。我做了两个函数来计算节点和链接。对于节点,我做了一个递归函数来获取所有独特的按键和分配id
每个键。而我又函数来获取所有键之间的关系。
let makeNodeObj = function(orObj, index){
let obj = {};
obj.nodeId = index;
obj.name = orObj;
return obj;
}
var getUniqueKeys = (old, arr)=>{
let toRet = old;
arr.forEach((data,index)=>{
if(toRet.indexOf(data.key)<0){ //remove duplicates
toRet.push(data.key);
}
if(data.buckets !== undefined && data.buckets.length>0){
getUniqueKeys(toRet, data.buckets);
}
});
return toRet;
}
let uniqueKeys = getUniqueKeys([],unformattedJson);
let nodes = uniqueKeys.map((data,index)=>{
return makeNodeObj(data,index);
});
let getNodeId = function(nodes, key){
let node = nodes.find((data)=>{
return data.name == key
});
return node.nodeId;
}
let links = [];
unformattedJson.map((data)=>{
let sourceId = getNodeId(nodes, data.key);
if(data.buckets.length>0){
data.buckets.map((data2)=>{
let targetId = getNodeId(nodes,data2.key);
let linkObj = {};
linkObj.source = sourceId;
linkObj.target = targetId;
linkObj.value = data2.value;
links.push(linkObj);
})
}
});
console.log({
nodes, links
});
如果有只有一个级别深桶我的解决方案才有效。如何做到这一点的孩子里面多重嵌套桶?
我为产生预期的输出递归方法。一个key
和他产生id
之间的关联都保持了Map。该方法使用与Deep First Search算法遍历树的主意。
let unformattedJson = [
{
"key": "a1",
"value": 30,
"buckets": [
{
"key": "a2",
"value": 10,
"buckets": [
{"key": "a3", "value": 99}
]
},
{"key": "b2", "value": 20}
]
},
{
"key": "b1",
"value": 70,
"buckets": [
{"key": "b2", "value": 40},
{"key": "c2", "value": 30}
]
}
];
const getData = (input, visited=new Map(), parent, nodes=[], links=[]) =>
{
input.forEach(x =>
{
// Add node into the node list, if not visited previosuly.
if (!visited.has(x.key))
{
let currId = nodes.length;
nodes.push({nodeId: currId, name: x.key});
visited.set(x.key, currId);
}
// If a parent node exists, add relation into the links list.
if (parent)
{
// Note, we use the "Map" to get the ids.
links.push({
source: visited.get(parent.key),
target: visited.get(x.key),
value: x.value
});
}
// Traverse (if required) to the next level of deep.
if (x.buckets)
getData(x.buckets, visited, x, nodes, links)
});
return {nodes: nodes, links: links};
}
console.log(getData(unformattedJson));