将平面 json 对象转换为分层 d3 对象结构

transform flat json object to hierarchical d3 object structure

我想将 json 数据如 packageJson 转换为适合树形图的分层格式

使用d3.hierarchy转换对象 据我了解 每个 属性 包含嵌套数据 需要使用 like

内部的辅助函数进行转换

const test = d3.hierarchy(packageJson, d => d.scripts);

是通过查找每个具有嵌套对象的 属性 来转换 packageJson 的唯一方法
或者是我不知道的任何 d3 辅助函数来完成这种任务?

const fam = d3.hierarchy({
    name: "root",
    children: [
      {name: "child #1"},
      {
        name: "child #2",
        children: [
          {name: "grandchild #1"},
          {name: "grandchild #2"},
          {name: "grandchild #3"}
        ]
      }
    ]
  })


// transform json 

const packageJson = {
  "name": "visualize-data",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.6.5",
    "d3-color": "^3.0.1",
    "d3-hierarchy": "^3.0.1",
    "d3-selection": "^3.0.0",
    "d3-shape": "^3.0.1",
    "vue": "^3.0.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/compiler-sfc": "^3.0.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^7.0.0"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/vue3-essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "babel-eslint"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}



const test = d3.hierarchy(packageJson, d => d.scripts);

console.log(test)
    #limit {
        max-width: 100px;
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>



 <div id="limit">

    </div>

最简单的方法是创建一个包含所有子属性的数组作为 d3.hierarchy:

使用的子数组
let root = d3.hierarchy(packageJson, function(d) {
  if(typeof d == "object")
   return Object.keys(d).map(k=>{ return d[k]; }); 
})

我们需要检查 属性 是否是对象(/数组)而不是文本,否则我们将提取标记叶节点的字符串的键。

这不是最令人满意的结果:没有节点有名称。我们可以通过根据父节点给它们的 属性 名称给节点命名来解决这个问题(根仍然是无名的,但这很容易纠正):

let root = d3.hierarchy(packageJson, function(d) {
  if(typeof d == "object")
   return Object.keys(d).filter(d=>d!="$name").map(k=>{
     if(typeof d[k] == "object") d[k].$name = k;
     else d[k] = k + " : " + d[k];
     return d[k];
   }); 
})

$name 属性 跟踪非叶节点的名称(因为这些是我们可以添加 属性 的对象),它被子数组生成器过滤掉。由于叶节点是字符串,我只是修改它们以反映 属性 名称和值:它们的值位于 d.data 而非叶节点的名称位于 d.data .$名字.

根据您想要的结果,您可能喜欢也可能不喜欢上述方法。但为了演示,如下所示:

这是一个演示:

const packageJson = {
  "name": "visualize-data",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.6.5",
    "d3-color": "^3.0.1",
    "d3-hierarchy": "^3.0.1",
    "d3-selection": "^3.0.0",
    "d3-shape": "^3.0.1",
    "vue": "^3.0.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/compiler-sfc": "^3.0.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^7.0.0"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/vue3-essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "babel-eslint"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

let root = d3.hierarchy(packageJson, function(d) {
  if(typeof d == "object")
   return Object.keys(d).filter(d=>d!="$name").map(k=>{
     if(typeof d[k] == "object") d[k].$name = k;
     else d[k] = k + " : " + d[k];
     return d[k];
   }); 
})


var width = 600;
var height = 300;

margin = {left: 10, top: 10, right: 50, bottom: 50}

var svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height);
      
var g = svg.append("g").attr('transform','translate('+ margin.left +','+ margin.right +')');

var tree = d3.tree()
    .size([height-margin.top-margin.bottom,width-margin.left-margin.right]);

 var link = g.selectAll(".link")
    .data(tree(root).links())
    .enter().append("path")
      .attr("class", "link")
      .attr("d", d3.linkHorizontal()
          .x(function(d) { return d.y; })
          .y(function(d) { return d.x; }));

  var node = g.selectAll(".node")
    .data(root.descendants())
    .enter().append("g")
      .attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
      .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })

  node.append("circle")
      .attr("r", 2.5);
      
  node.append("text")
     .text(function(d) { return d.data.$name || d.data; })
     .attr('y',-10)
     .attr('x',-10)
     .attr('text-anchor','middle');
.node circle {
          fill: #fff;
          stroke: steelblue;
          stroke-width: 3px;
        }

        .link {
          fill: none;
          stroke: #ccc;
          stroke-width: 2px;
        }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>