{"id":"22ac214fe83d7053","slug":"hierarchical-edge-bundling","first_public_version":428,"paused_version":439,"likes":238,"publish_level":"live","forks":331,"fork_of":null,"has_importers":true,"thumbnail":"d7d8e2b1214b0fd8e5230f88658d9196649cc7504506b18ed0c50eefb523d916","default_thumbnail":"d7d8e2b1214b0fd8e5230f88658d9196649cc7504506b18ed0c50eefb523d916","update_time":"2020-08-26T22:40:36.618Z","publish_time":"2023-04-10T02:09:57.042Z","publish_version":439,"latest_version":439,"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":""}],"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-10-29T23:52:01.123Z","mime_type":"application/json","status":"public","size":34321,"content_encoding":"gzip","private_bucket_id":null}],"comments":[{"id":"b40e5e9be249260d","content":"In `[, d]`  what does the \",\" means ?","node_id":10,"create_time":"2022-03-09T21:28:19.148Z","update_time":null,"resolved":true,"user":{"id":"8d69a65fe65fc281","avatar_url":"https://avatars.observableusercontent.com/avatar/ff089ba737c0f2e0f698a2f28473ca64b313541f039be318d66fb9ae49ef8af9","login":"ashilex","name":"Alessandro Sassi","bio":"","home_url":"","tier":"public"}},{"id":"d16437e81d9d2270","content":"Hi, I cannot understand why \nfind({name: name.substring(0, i), children: []}).children.push(data);\nmodifies the map.\n\nThere is no set() method that modifies it. \n\nVery confused !","node_id":253,"create_time":"2021-11-27T18:54:31.713Z","update_time":null,"resolved":true,"user":{"id":"8d69a65fe65fc281","avatar_url":"https://avatars.observableusercontent.com/avatar/ff089ba737c0f2e0f698a2f28473ca64b313541f039be318d66fb9ae49ef8af9","login":"ashilex","name":"Alessandro Sassi","bio":"","home_url":"","tier":"public"}},{"id":"d9ed2da4bb4ad408","content":"It is a recursive call to the function itself. The map is modified when map.set is called.\nNote that not all calls to find is with the root data map. The recursive calls are with empty children.","node_id":253,"create_time":"2021-12-15T22:01:35.163Z","update_time":null,"resolved":true,"user":{"id":"99fe6a4d1d877dd3","avatar_url":"https://avatars.observableusercontent.com/avatar/7e4a9aca84ced4c64d9d4747f14f9b585aa23568898eee332124e62c1790a49c","login":"99fe6a4d1d877dd3","name":"Karl-Johan V. Jensen","bio":"","home_url":"","tier":"public"}}],"commenting_lock":null,"suggestions_to":[],"suggestion_from":null,"collections":[{"id":"d9df4fb5263ace62","type":"public","slug":"visualization","title":"Visualization","description":"Explore and explain patterns in quantitative data using D3, Vega, and Observable Plot","update_time":"2023-07-19T17:50:40.230Z","pinned":true,"ordered":true,"custom_thumbnail":"09e385d95ce7df7d392b0133d68e97dd5675378190775d038d21516ea62178ba","default_thumbnail":"32ad600cf556f7b9991b2a11f7b6b8e55abe7ada6062ef88a5a7de422bb261ab","thumbnail":"09e385d95ce7df7d392b0133d68e97dd5675378190775d038d21516ea62178ba","listing_count":84,"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":"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":"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"}}],"version":426,"title":"Hierarchical Edge Bundling","license":"isc","copyright":"Copyright 2018–2020 Observable, Inc.","nodes":[{"id":1,"value":"md`# Hierarchical Edge Bundling\n\nThis chart shows relationships among classes in a software hierarchy. Hover a class to reveal its imports (<b style=\"color: ${colorout};\">outgoing</b> edges) and classes that import it (<b style=\"color: ${colorin};\">incoming</b> edges).`","pinned":false,"mode":"js","data":null,"name":null},{"id":10,"value":"chart = {\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(\"viewBox\", [-width / 2, -width / 2, width, width]);\n\n  const node = svg.append(\"g\")\n      .attr(\"font-family\", \"sans-serif\")\n      .attr(\"font-size\", 10)\n    .selectAll(\"g\")\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      .each(function(d) { d.text = this; })\n      .on(\"mouseover\", overed)\n      .on(\"mouseout\", outed)\n      .call(text => text.append(\"title\").text(d => `${id(d)}\n${d.outgoing.length} outgoing\n${d.incoming.length} incoming`));\n\n  const link = svg.append(\"g\")\n      .attr(\"stroke\", colornone)\n      .attr(\"fill\", \"none\")\n    .selectAll(\"path\")\n    .data(root.leaves().flatMap(leaf => leaf.outgoing))\n    .join(\"path\")\n      .style(\"mix-blend-mode\", \"multiply\")\n      .attr(\"d\", ([i, o]) => line(i.path(o)))\n      .each(function(d) { d.path = this; });\n\n  function overed(event, d) {\n    link.style(\"mix-blend-mode\", null);\n    d3.select(this).attr(\"font-weight\", \"bold\");\n    d3.selectAll(d.incoming.map(d => d.path)).attr(\"stroke\", colorin).raise();\n    d3.selectAll(d.incoming.map(([d]) => d.text)).attr(\"fill\", colorin).attr(\"font-weight\", \"bold\");\n    d3.selectAll(d.outgoing.map(d => d.path)).attr(\"stroke\", colorout).raise();\n    d3.selectAll(d.outgoing.map(([, d]) => d.text)).attr(\"fill\", colorout).attr(\"font-weight\", \"bold\");\n  }\n\n  function outed(event, d) {\n    link.style(\"mix-blend-mode\", \"multiply\");\n    d3.select(this).attr(\"font-weight\", null);\n    d3.selectAll(d.incoming.map(d => d.path)).attr(\"stroke\", null);\n    d3.selectAll(d.incoming.map(([d]) => d.text)).attr(\"fill\", null).attr(\"font-weight\", null);\n    d3.selectAll(d.outgoing.map(d => d.path)).attr(\"stroke\", null);\n    d3.selectAll(d.outgoing.map(([, d]) => d.text)).attr(\"fill\", null).attr(\"font-weight\", null);\n  }\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":332,"value":"colorin = \"#00f\"","pinned":true,"mode":"js","data":null,"name":null},{"id":334,"value":"colorout = \"#f00\"","pinned":true,"mode":"js","data":null,"name":null},{"id":336,"value":"colornone = \"#ccc\"","pinned":true,"mode":"js","data":null,"name":null},{"id":99,"value":"width = 954","pinned":true,"mode":"js","data":null,"name":null},{"id":101,"value":"radius = width / 2","pinned":true,"mode":"js","data":null,"name":null},{"id":157,"value":"line = d3.lineRadial()\n    .curve(d3.curveBundle.beta(0.85))\n    .radius(d => d.y)\n    .angle(d => d.x)","pinned":true,"mode":"js","data":null,"name":null},{"id":104,"value":"tree = d3.cluster()\n    .size([2 * Math.PI, radius - 100])","pinned":true,"mode":"js","data":null,"name":null},{"id":7,"value":"d3 = require(\"d3@6\")","pinned":true,"mode":"js","data":null,"name":null}],"resolutions":[],"schedule":null,"last_view_time":null}