{"id":"16ea6ba8fc487e0a","slug":"hierarchical-edge-bundling/2","trashed":false,"description":"","likes":127,"publish_level":"live","forks":119,"fork_of":{"id":"22ac214fe83d7053","slug":"hierarchical-edge-bundling","title":"Hierarchical edge bundling","owner":{"id":"863951e3ebe4c0ae","avatar_url":"https://avatars.observableusercontent.com/avatar/5af16e327a90b2873351dda8a596c0d2d3bf954f64523deefe80177c9764d0f7","login":"d3","name":"D3","bio":"Bring your data to life.","home_url":"https://d3js.org","type":"team","tier":"pro_2024"},"version":413},"has_importers":true,"update_time":"2023-08-01T14:22:55.410Z","first_public_version":778,"paused_version":798,"publish_time":"2023-04-10T02:10:38.200Z","publish_version":798,"latest_version":798,"thumbnail":"a2dce874e981b7c74f45e81baa75305b04c73da9937761c224919216859691a7","default_thumbnail":"a2dce874e981b7c74f45e81baa75305b04c73da9937761c224919216859691a7","roles":[],"sharing":null,"owner":{"id":"863951e3ebe4c0ae","avatar_url":"https://avatars.observableusercontent.com/avatar/5af16e327a90b2873351dda8a596c0d2d3bf954f64523deefe80177c9764d0f7","login":"d3","name":"D3","bio":"Bring your data to life.","home_url":"https://d3js.org","type":"team","tier":"pro_2024"},"creator":{"id":"074c414ad1d825f5","avatar_url":"https://avatars.observableusercontent.com/avatar/82811927da99f8938001b2ef1f552ad2c47083e46ebc55a3a146a5a5848c4519","login":"mbostock","name":"Mike Bostock","bio":"Visualization toolmaker. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","tier":"pro"},"authors":[{"id":"074c414ad1d825f5","avatar_url":"https://avatars.observableusercontent.com/avatar/82811927da99f8938001b2ef1f552ad2c47083e46ebc55a3a146a5a5848c4519","name":"Mike Bostock","login":"mbostock","bio":"Visualization toolmaker. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","tier":"pro","approved":true,"description":""}],"collections":[{"id":"1b0fbc3b1939d7fa","type":"public","slug":"gallery","title":"Gallery","description":"Examples featured in the D3 gallery","update_time":"2023-04-10T02:28:10.124Z","pinned":false,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"6953adf7aec9d29339751fa68d9c9dc67169a0ffd7469f9a4055eda920bdd2a4","thumbnail":"6953adf7aec9d29339751fa68d9c9dc67169a0ffd7469f9a4055eda920bdd2a4","listing_count":147,"parent_collection_count":0,"owner":{"id":"863951e3ebe4c0ae","avatar_url":"https://avatars.observableusercontent.com/avatar/5af16e327a90b2873351dda8a596c0d2d3bf954f64523deefe80177c9764d0f7","login":"d3","name":"D3","bio":"Bring your data to life.","home_url":"https://d3js.org","type":"team","tier":"pro_2024"}},{"id":"914e2880013747e9","type":"public","slug":"data-visualization-for-developers","title":"Observable for Developers","description":"Examples and demos on how developers can use get their work done in Observable","update_time":"2022-04-07T20:15:51.164Z","pinned":false,"ordered":true,"custom_thumbnail":null,"default_thumbnail":"9f3d35bbbc7336d9fbb1db0e685e486e8079c2509409f558dc0a4f57c9c3e97a","thumbnail":"9f3d35bbbc7336d9fbb1db0e685e486e8079c2509409f558dc0a4f57c9c3e97a","listing_count":24,"parent_collection_count":1,"owner":{"id":"f35c755083683fe5","avatar_url":"https://avatars.observableusercontent.com/avatar/5a51c3b908225a581d20577e488e2aba8cbc9541c52982c638638c370c3e5e8e","login":"observablehq","name":"Observable","bio":"The end-to-end solution for building and hosting better data apps, dashboards, and reports.","home_url":"https://observablehq.com","type":"team","tier":"enterprise_2024"}},{"id":"ac3c6ed33ef3345f","type":"public","slug":"d3-hierarchy","title":"d3-hierarchy","description":"2D layout algorithms for visualizing hierarchical data.","update_time":"2019-03-07T07:26:12.604Z","pinned":true,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"d1bb6653143239319cfee07a6f362c108ad0ddc9e7c064ebf185364992c2426c","thumbnail":"d1bb6653143239319cfee07a6f362c108ad0ddc9e7c064ebf185364992c2426c","listing_count":35,"parent_collection_count":1,"owner":{"id":"863951e3ebe4c0ae","avatar_url":"https://avatars.observableusercontent.com/avatar/5af16e327a90b2873351dda8a596c0d2d3bf954f64523deefe80177c9764d0f7","login":"d3","name":"D3","bio":"Bring your data to life.","home_url":"https://d3js.org","type":"team","tier":"pro_2024"}}],"files":[{"id":"9b6806e3dd9c4c2c26760ba784437138c78b43a9a8e58a0bbafe5833026e3265637c9c7810224d66b79ba907b4d0be731c1a81ad043e10376aec3c18a49f3d84","url":"https://static.observableusercontent.com/files/9b6806e3dd9c4c2c26760ba784437138c78b43a9a8e58a0bbafe5833026e3265637c9c7810224d66b79ba907b4d0be731c1a81ad043e10376aec3c18a49f3d84","download_url":"https://static.observableusercontent.com/files/9b6806e3dd9c4c2c26760ba784437138c78b43a9a8e58a0bbafe5833026e3265637c9c7810224d66b79ba907b4d0be731c1a81ad043e10376aec3c18a49f3d84?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27flare.json","name":"flare.json","create_time":"2019-11-09T18:53:48.447Z","mime_type":"application/json","status":"public","size":34321,"content_encoding":"gzip","private_bucket_id":null}],"comments":[],"commenting_lock":null,"suggestion_from":null,"suggestions_to":[],"version":798,"title":"Hierarchical edge bundling II","license":"isc","copyright":"Copyright 2019–2020 Observable, Inc.","nodes":[{"id":1,"value":"<div style=\"color: grey; font: 13px/25.5px var(--sans-serif); text-transform: uppercase;\"><h1 style=\"display: none;\">Hierarchical edge bundling II</h1><a href=\"https://d3js.org/\">D3</a> › <a href=\"/@d3/gallery\">Gallery</a></div>\n\n# Hierarchical edge bundling II\n\nThis chart shows relationships among classes in a software hierarchy. Each directed edge, going from <b style=\"color: ${color(0.1)};\">source</b> to <b style=\"color: ${color(0.9)};\">target</b>, corresponds to an import.","pinned":false,"mode":"md","data":{},"name":""},{"id":10,"value":"chart = {\n  const width = 954;\n  const radius = width / 2;\n  const k = 6; // 2^k colors segments per curve\n\n  const tree = d3.cluster()\n      .size([2 * Math.PI, radius - 100]);\n  const root = tree(bilink(d3.hierarchy(data)\n      .sort((a, b) => d3.ascending(a.height, b.height) || d3.ascending(a.data.name, b.data.name))));\n\n  const svg = d3.create(\"svg\")\n      .attr(\"width\", width)\n      .attr(\"height\", width)\n      .attr(\"viewBox\", [-width / 2, -width / 2, width, width])\n      .attr(\"style\", \"max-width: 100%; height: auto; font: 10px sans-serif;\");\n\n  const node = svg.append(\"g\")\n    .selectAll()\n    .data(root.leaves())\n    .join(\"g\")\n      .attr(\"transform\", d => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`)\n    .append(\"text\")\n      .attr(\"dy\", \"0.31em\")\n      .attr(\"x\", d => d.x < Math.PI ? 6 : -6)\n      .attr(\"text-anchor\", d => d.x < Math.PI ? \"start\" : \"end\")\n      .attr(\"transform\", d => d.x >= Math.PI ? \"rotate(180)\" : null)\n      .text(d => d.data.name)\n      .call(text => text.append(\"title\").text(d => `${id(d)}\n${d.outgoing.length} outgoing\n${d.incoming.length} incoming`));\n\n  const line = d3.lineRadial()\n      .curve(d3.curveBundle)\n      .radius(d => d.y)\n      .angle(d => d.x);\n\n  const path = ([source, target]) => {\n    const p = new Path;\n    line.context(p)(source.path(target));\n    return p;\n  };\n\n  svg.append(\"g\")\n      .attr(\"fill\", \"none\")\n    .selectAll()\n    .data(d3.transpose(root.leaves()\n      .flatMap(leaf => leaf.outgoing.map(path))\n      .map(path => Array.from(path.split(k)))))\n    .join(\"path\")\n      .style(\"mix-blend-mode\", \"darken\")\n      .attr(\"stroke\", (d, i) => color(d3.easeQuad(i / ((1 << k) - 1))))\n      .attr(\"d\", d => d.join(\"\"));\n\n  return svg.node();\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":3,"value":"data = hierarchy(await FileAttachment(\"flare.json\").json())","pinned":true,"mode":"js","data":null,"name":null},{"id":253,"value":"function hierarchy(data, delimiter = \".\") {\n  let root;\n  const map = new Map;\n  data.forEach(function find(data) {\n    const {name} = data;\n    if (map.has(name)) return map.get(name);\n    const i = name.lastIndexOf(delimiter);\n    map.set(name, data);\n    if (i >= 0) {\n      find({name: name.substring(0, i), children: []}).children.push(data);\n      data.name = name.substring(i + 1);\n    } else {\n      root = data;\n    }\n    return data;\n  });\n  return root;\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":280,"value":"function bilink(root) {\n  const map = new Map(root.leaves().map(d => [id(d), d]));\n  for (const d of root.leaves()) d.incoming = [], d.outgoing = d.data.imports.map(i => [d, map.get(i)]);\n  for (const d of root.leaves()) for (const o of d.outgoing) o[1].incoming.push(o);\n  return root;\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":180,"value":"function id(node) {\n  return `${node.parent ? id(node.parent) + \".\" : \"\"}${node.data.name}`;\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":676,"value":"class Path {\n  constructor(_) {\n    this._ = _;\n    this._m = undefined;\n  }\n  moveTo(x, y) {\n    this._ = [];\n    this._m = [x, y];\n  }\n  lineTo(x, y) {\n    this._.push(new Line(this._m, this._m = [x, y]));\n  }\n  bezierCurveTo(ax, ay, bx, by, x, y) {\n    this._.push(new BezierCurve(this._m, [ax, ay], [bx, by], this._m = [x, y]));\n  }\n  *split(k = 0) {\n    const n = this._.length;\n    const i = Math.floor(n / 2);\n    const j = Math.ceil(n / 2);\n    const a = new Path(this._.slice(0, i));\n    const b = new Path(this._.slice(j));\n    if (i !== j) {\n      const [ab, ba] = this._[i].split();\n      a._.push(ab);\n      b._.unshift(ba);\n    }\n    if (k > 1) {\n      yield* a.split(k - 1);\n      yield* b.split(k - 1);\n    } else {\n      yield a;\n      yield b;\n    }\n  }\n  toString() {\n    return this._.join(\"\");\n  }\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":683,"value":"class Line {\n  constructor(a, b) {\n    this.a = a;\n    this.b = b;\n  }\n  split() {\n    const {a, b} = this;\n    const m = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];\n    return [new Line(a, m), new Line(m, b)];\n  }\n  toString() {\n    return `M${this.a}L${this.b}`;\n  }\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":685,"value":"BezierCurve = {\n  const l1 = [4 / 8, 4 / 8, 0 / 8, 0 / 8];\n  const l2 = [2 / 8, 4 / 8, 2 / 8, 0 / 8];\n  const l3 = [1 / 8, 3 / 8, 3 / 8, 1 / 8];\n  const r1 = [0 / 8, 2 / 8, 4 / 8, 2 / 8];\n  const r2 = [0 / 8, 0 / 8, 4 / 8, 4 / 8];\n\n  function dot([ka, kb, kc, kd], {a, b, c, d}) {\n    return [\n      ka * a[0] + kb * b[0] + kc * c[0] + kd * d[0],\n      ka * a[1] + kb * b[1] + kc * c[1] + kd * d[1]\n    ];\n  }\n\n  return class BezierCurve {\n    constructor(a, b, c, d) {\n      this.a = a;\n      this.b = b;\n      this.c = c;\n      this.d = d;\n    }\n    split() {\n      const m = dot(l3, this);\n      return [\n        new BezierCurve(this.a, dot(l1, this), dot(l2, this), m),\n        new BezierCurve(m, dot(r1, this), dot(r2, this), this.d)\n      ];\n    }\n    toString() {\n      return `M${this.a}C${this.b},${this.c},${this.d}`;\n    }\n  };\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":588,"value":"color = t => d3.interpolateRdBu(1 - t)","pinned":true,"mode":"js","data":null,"name":null}],"resolutions":[],"schedule":null,"last_view_time":null}