16

我正在使用 d3.js 将动物(有机体)家族(一次最多 4000 个)可视化为树形图,尽管数据源也可以是目录列表或命名空间对象列表。我的数据看起来像:

json = {
    organisms:[
        {name: 'Hemiptera.Miridae.Kanakamiris'},
        {name: 'Hemiptera.Miridae.Neophloeobia.incisa'},
        {name: 'Lepidoptera.Nymphalidae.Ephinephile.rawnsleyi'},
        ... etc ...
    ]
}

我的问题是:我正在尝试找到将上述数据转换为分层父/子数据结构的最佳方法,这些数据结构被许多 d3 可视化(例如树形图)使用(例如,请参阅d3/ 中的flare.json )示例/数据/目录)。这是所需数据结构的示例:

{"name": "ROOT",
 "children": [
        {"name": "Hemiptera",
         "children": [
             {"name": "Miridae",
              "children": [
                  {"name": "Kanakamiris", "children":[]},
                  {"name": "Neophloeobia",
                   "children": [
                       {"name": "incisa", "children":[] }
                   ]}
              ]}
         ]},
        {"name": "Lepidoptera",
         "children": [
             {"name": "Nymphalidae",
              "children": [
                  {"name": "Ephinephile",
                   "children": [
                       {"name": "rawnsleyi", "children":[] }
                   ]}
              ]}
         ]}
    ]}
}

编辑:将所有原始所需的数据结构包含在一个ROOT节点内,以符合只有一个主父节点的 d3 示例的结构。

我希望了解一般的设计模式,作为奖励,我很想在 javascript、php(甚至 python)中看到一些解决方案。javascript是我的偏好。关于 php:我实际使用的数据来自一个将结果编码为 json 的 php 脚本对数据库的调用。如果这对基于 php 的答案有任何用途,则 php 脚本中的数据库结果是一个有序数组(见下文)。

Array
(
    [0] => Array
        (
            ['Rank_Order'] => 'Hemiptera'
            ['Rank_Family'] => 'Miridae'
            ['Rank_Genus'] => 'Kanakamiris'
            ['Rank_Species'] => ''
        ) ........

其中: 'Rank_Order'isParentOf 'Rank_Family'isParentOf 'Rank_Genus'isParentOf'Rank_Species'

我在这里问了一个关于 php 解决方案的类似问题,但唯一的答案在我的服务器上不起作用,我不太明白发生了什么,所以我想从设计模式的角度提出这个问题,并包括参考到我在 javascript 和 d3.js 中的实际使用。

4

3 回答 3

7

以下内容特定于您提供的结构,可以很容易地使其更通用。我确信addChild函数可以简化。希望评论对您有所帮助。

function toHeirarchy(obj) {

  // Get the organisms array
  var orgName, orgNames = obj.organisms;

  // Make root object
  var root = {name:'ROOT', children:[]};

  // For each organism, get the name parts
  for (var i=0, iLen=orgNames.length; i<iLen; i++) {
    orgName = orgNames[i].name.split('.');

    // Start from root.children
    children = root.children;

    // For each part of name, get child if already have it
    // or add new object and child if not
    for (var j=0, jLen=orgName.length; j<jLen; j++) {
      children = addChild(children, orgName[j]);      
    }
  }
  return root;

  // Helper function, iterates over children looking for 
  // name. If found, returns its child array, otherwise adds a new
  // child object and child array and returns it.
  function addChild(children, name) {

    // Look for name in children
    for (var i=0, iLen=children.length; i<iLen; i++) {

      // If find name, return its child array
      if (children[i].name == name) {
        return children[i].children;        
      }
    }
    // If didn't find name, add a new object and 
    // return its child array
    children.push({'name': name, 'children':[]});
    return children[children.length - 1].children;
  }
}
于 2012-08-26T07:57:35.967 回答
5

鉴于您的起始输入,我相信类似以下代码的内容会产生您想要的输出。我不认为这是最漂亮的方法,但这是当时想到的。

预处理数据似乎最简单,首先将初始字符串数组拆分为数组数组,如下所示:

[
   ["Hemiptera","Miridae","Kanakamiris" ],
   ["Hemiptera","Miridae","Neophloeobia","incisa" ],
   //etc
]

...然后对其进行处理以获取如下形式的工作对象:

  working = {
       Hemiptera : {
           Miridae : {
              Kanakamiris : {},
              Neophloeobia : {
                  incisa : {}
              }
           }
       },
       Lepidoptera : {
           Nymphalidae : {
              Ephinephile : {
                  rawnsleyi : {}
              }
           }
       }
    }

...因为使用对象而不是数组可以更轻松地测试子项是否已经存在。创建了上述结构后,我最后一次处理它以获得最终所需的输出。所以:

// start by remapping the data to an array of arrays
var organisms = data.organisms.map(function(v) {
        return v.name.split(".");
    });

// this function recursively processes the above array of arrays
// to create an object whose properties are also objects
function addToHeirarchy(val, level, heirarchy) {
    if (val[level]) {
        if (!heirarchy.hasOwnProperty(val[level]))
            heirarchy[val[level]] = {};
        addToHeirarchy(val, level + 1, heirarchy[val[level]]);
    }
}
var working = {};    
for (var i = 0; i < organisms.length; i++)
    addToHeirarchy(organisms[i], 0, working);

// this function recursively processes the object created above
// to create the desired final structure
function remapHeirarchy(item) {
    var children = [];
    for (var k in item) {
        children.push({
            "name" : k,
            "children" : remapHeirarchy(item[k])
        });
    }
    return children;
}

var heirarchy = {
    "name" : "ROOT",
    "children" : remapHeirarchy(working)
};

演示:http: //jsfiddle.net/a669F/1/

于 2012-08-26T03:12:07.007 回答
2

我自己的问题的另一种答案....在过去的一天里,我对 d3.js 的了解不多,与这个问题d3.nest()和 .key() 和 .entries() 的关系是我的朋友(所有 d3 功能)。该答案涉及更改初始数据,因此它可能不能作为我提出的特定问题的良好答案。但是,如果有人有类似的问题并且可以更改服务器上的内容,那么这是一个非常简单的解决方案:

以这种格式从数据库中返回数据:

json = {'Organisms': [
    { 'Rank_Order': 'Hemiptera',
      'Rank_Family': 'Miridae',
      'Rank_Genus': 'Kanakamiris',
      'Rank_Species': '' },
    {}, ...
]}

然后使用d3.nest()

organismNest = d3.nest()
    .key(function(d){return d.Rank_Order;})
    .key(function(d){return d.Rank_Family;})
    .key(function(d){return d.Rank_Genus;})
    .key(function(d){return d.Rank_Species;})
    .entries(json.Organism);

这返回:

{
key: "Hemiptera"
  values: [
    {
      key: "Cicadidae"
      values: [
        {
          key: "Pauropsalta "
          values: [
            {
              key: "siccanus"
              values: [
                       Rank_Family: "Cicadidae"
                       Rank_Genus: "Pauropsalta "
                       Rank_Order: "Hemiptera"
                       Rank_Species: "siccanus"
                       AnotherOriginalDataKey: "original data value"

etc etc, nested and lovely

这返回的内容与我在上面问题中描述为我想要的格式的它们的数组非常相似,但有一些区别。特别是,没有所有封闭的 ROOT 元素,而且我最初想要的键是“name”和“children”。nest() 分别将键返回为“key”和“values”。这些替代键很容易在 d3.js 中使用,只需定义适当的数据访问器函数(基本 d3 概念)......但这超出了问题的原始范围......希望对某人也有帮助

于 2012-09-01T03:37:57.390 回答