| 143 | } |
| 144 | |
| 145 | function ClusterTree(data) { |
| 146 | const width = window.innerWidth - 20; |
| 147 | const root = d3.hierarchy(data); |
| 148 | const dx = 20; |
| 149 | const dy = width / (root.height + 1); |
| 150 | const tree = d3.cluster().nodeSize([dx, dy]); |
| 151 | root.sort((a, b) => d3.ascending(a.data.name, b.data.name)); |
| 152 | tree(root); |
| 153 | let x0 = Infinity; |
| 154 | let x1 = -x0; |
| 155 | root.each(d => { |
| 156 | if (d.x > x1) x1 = d.x; |
| 157 | if (d.x < x0) x0 = d.x; |
| 158 | }); |
| 159 | const height = x1 - x0 + dx * 2; |
| 160 | |
| 161 | const svg = d3.create("svg") |
| 162 | .attr("width", width) |
| 163 | .attr("height", height) |
| 164 | .attr("viewBox", [-dy / 3, x0 - dx, width, height]) |
| 165 | .attr("style", `max-width: 100%; height: auto; font: ${gD3fontSize}px sans-serif;`); |
| 166 | |
| 167 | const link = svg.append("g") |
| 168 | .attr("fill", "none") |
| 169 | .attr("stroke", "#555") |
| 170 | .attr("stroke-opacity", 0.4) |
| 171 | .attr("stroke-width", 1.5) |
| 172 | .selectAll() |
| 173 | .data(root.links()) |
| 174 | .join("path") |
| 175 | .attr("d", d3.linkHorizontal() |
| 176 | .x(d => d.y) |
| 177 | .y(d => d.x)); |
| 178 | |
| 179 | const node = svg.append("g") |
| 180 | .attr("stroke-linejoin", "round") |
| 181 | .attr("stroke-width", 3) |
| 182 | .selectAll() |
| 183 | .data(root.descendants()) |
| 184 | .join("g") |
| 185 | .attr("transform", d => `translate(${d.y},${d.x})`); |
| 186 | |
| 187 | node.append("circle") |
| 188 | .attr("fill", d => d.children ? "#555" : "#999") |
| 189 | .attr("r", 2.5); |
| 190 | |
| 191 | node.append("text") |
| 192 | .attr("dy", "0.31em") |
| 193 | .attr("x", d => d.children ? -6 : 6) |
| 194 | .attr("text-anchor", d => d.children ? "end" : "start") |
| 195 | .text(d => d.data.name) |
| 196 | .attr("stroke", "white") |
| 197 | .attr("paint-order", "stroke"); |
| 198 | |
| 199 | return svg.node(); |
| 200 | } |
| 201 | |
| 202 | function RadialTidyTree(data) { |