version

D3:6.3.1 React: 16.8.6 reference: observablehq.com/nitaku/tan…

rendering

npm install d3

Introduce D3 in the required components page

import * as d3 from 'd3';
Copy the code

Initialize the Simulation

componentDidMount() { this.initSimulation(); } initSimulation = () => { const width = this.chartRef.current.clientWidth; const height = this.chartRef.current.clientHeight; // Initialize simulation. If force is specified, add force(mechanical model) with name and return simulation. This. Simulation = d3.forcesimulation () // This. Simulation = d3.forcesimulation () // The array of nodes to be referenced is specified. d3.forceCenter(width / 2, height / 2)) .force('collide', d3.forceCollide().radius(() => 60)) .force('yPos', d3.forceY().strength(1)) .force('xPos', d3.forceX().strength(1)) .force('charge', d3.forceManyBody().strength(-520)); // Draw SVG, This.svg = d3. select('#theChart').append(' SVG ') // Create SVG. Attr ('width', Width).attr('height', height * 0.9).call(this.zoom()); // zoom this.graph = this.svg. Append ('g'); }; render() { return ( <div className="theChart" id="theChart" ref={this.chartRef} style={{ width: 600, height: 600 }} ></div> ); }Copy the code

Add Nodes to simulation after obtaining the data

// Const levels = [[{id: 'Chaos'}], parents: ['Chaos']}], parent: ['Chaos']}, parent: ['Chaos']} 'Oceanus', parents: ['Gaea'] }, { id: 'Thethys', parents: ['Gaea'] }, { id: 'Pontus' }, { id: 'Rhea', parents: ['Gaea'] }, { id: 'Cronus', parents: ['Gaea'] }, { id: 'Coeus', parents: ['Gaea'] }, { id: 'Phoebe', parents: ['Gaea'] }, { id: 'Crius', parents: ['Gaea'] }, { id: 'Hyperion', parents: ['Gaea'] }, { id: 'Iapetus', parents: ['Gaea']' }, { id: 'Thea', parents: ['Gaea'] }, { id: 'Themis', parents: ['Gaea'] }, { id: 'Mnemosyne', parents: ['Gaea']}] const data = this.tranferData(d3, levels); this.simulation.nodes = data.nodes; this.draw(data);Copy the code

Draw node connections

Draw = data => {const color = d3.scaleordinal (d3.schemedark2); draw = data => {const color = d3.scaleordinal (d3.schemedark2); / / add color data. Bundles. The map (b = > {const d = b.l inks. The map (l = > ` M ${t} l.x ${t} l.y l ${1} l.x b - l.c ${t} l.y A ${1} l.c ${1} l.c  90 0 1 ${l.xb} ${l.yt + l.c1} L${l.xb} ${l.ys - l.c2} A${l.c2} ${l.c2} 90 0 0 ${l.xb + l.c2} ${l.ys} L${l.xs} ${l.ys}` ) .join(''); this.svg .select('g') .append('path') .attr('class', 'topo-line') .style('fill', 'none') .attr('d', d) .attr('stroke', 'white') .attr('stroke-width', 5); this.svg .select('g') .append('path') .attr('class', 'topo-line'') .style('fill', 'none') .attr('d', d) .attr('stroke', Color (b.id)) // Line color.attr ('stroke-width', 2); }); Const line = this.svg. Select ('g').append('line'). Style ('stroke-linecap', 'round') .attr('stroke-width', 8) .attr('x1', n.x) .attr('y1', n.y - n.height / 2) .attr('x2', n.x) .attr('stroke', color(n.id)) .attr('y2', n.y + n.height / 2); This.svg. Select ('g').append('line').style('stroke-linecap', 'round').attr('stroke', 'round').attr('stroke', 'round') 'white') .attr('stroke-width', 4) .attr('x1', n.x) .attr('y1', n.y - n.height / 2) .attr('x2', n.x) .attr('y2', n.y + n.height / 2); Select ('g').append('text').style('font-size', '10px').attr('stroke', 'white') .attr('stroke-width', 2) .attr('x', n.x + 4) .attr('y', n.y - n.height / 2 - 4) .text(n.id); This. SVG. Select (' g '), append (' text '). Attr (' fill ', 'black') / / text color style (' the font, size, '10 px), style (' cursor', 'pointer') .attr('x', n.x + 4) .attr('y', n.y - n.height / 2 - 4) .text(n.id) .on('click', If (this.props. ItemClick) {this.props. ItemClick (n.id); }}); }); };Copy the code

Zoom in on

Zoom = () => d3.zoom ().scaleextent ([1/10, 10]) // Set the maximum scale. On ('start', this.onzoomstart). On ('zoom', this.zooming) .on('end', this.onZoomEnd); onZoomStart = (event, d) => { // console.log('start zoom'); }; Zooming = (event, d) => {// Zoom and drag the entire g this.graph.attr('transform', event.transform); // Get the scaling factor and coordinate of g. }; onZoomEnd = () => { // console.log('zoom end'); };Copy the code

Drag and drop

Drag = () => d3.drag ().on('start', this.ondragstart).on('drag', this.ondragstart) // Dragging process.on('end', this.ondragend); dragging = () => D3.drag ().on('start', this.ondragstart).on('drag', this.ondragend) onDragStart = (event, d) => { if (! Event.active) {this.simulation.alphatarget (1) {this.simulation.alphatarget (1) {this.simulation.alphatarget (1); // After dragging the node, restart the simulation} d.fx = d.x; // d.x is the current position, d.fx is the rest position d.fy = d.y; }; dragging = (event, d) => { d.fx = event.x; d.fy = event.y; }; onDragEnd = (event, d) => { if (! event.active) this.simulation.alphaTarget(0); d.fx = null; // Remove the fixed coordinate in the partying state. };Copy the code

Customize the icon behind the line

  1. Define ICONS and append them to SVG.
// Draw the cross after the line. const defs = this.svg.append('defs'); const marker = defs .append('marker') .attr('id', 'close') .attr('viewBox', '0 0 1024 1024') .attr('markerWidth', '1.5'). Attr (' markerHeight ', '1.5'). Attr (' refX ', '512'). Attr (' refY ', '512'). Attr (' received ', 'auto'); marker .append('path') .attr( 'd', 'M810.666667 273.493333L750.506667 213.333333 512 451.84 273.49333333 213.333333 273.493333 451.84 512 213.333333 750.506667 273.493333 810.666667 512 572.16 750.506667 810.666667 810.666667 750.506667 572.16 512z') // Draw a cross  .attr('fill', 'red'); // Add colorCopy the code
  1. Use, just add the marker-end attribute
line.attr('marker-end', 'url(#close)');
Copy the code

tranferData

tranferData = (d3, data) => { // precompute level depth data.forEach((l, i) => l.forEach(n => (n.level = i))); const nodes = data.reduce((a, x) => a.concat(x), []); const nodes_index = {}; nodes.forEach(d => (nodes_index[d.id] = d)); // objectification nodes.forEach(d => { d.parents = (d.parents === undefined ? [] : d.parents).map( p => nodes_index[p] ); }); // precompute bundles data.forEach((l, i) => { const index = {}; l.forEach(n => { if (n.parents.length === 0) { return; } const id = n.parents .map(d => d.id) .sort() .join('--'); if (id in index) { index[id].parents = index[id].parents.concat(n.parents); } else { index[id] = { id, parents: n.parents.slice(), level: i }; } n.bundle = index[id]; }); l.bundles = Object.keys(index).map(k => index[k]); l.bundles.forEach((b, i) => (b.i = i)); }); const links = []; nodes.forEach(d => { d.parents.forEach(p => links.push({ source: d, bundle: d.bundle, target: p }) ); }); const bundles = data.reduce((a, x) => a.concat(x.bundles), []); // reverse pointer from parent to bundles bundles.forEach(b => b.parents.forEach(p => { if (p.bundles_index === undefined) { p.bundles_index = {}; } if (! (b.id in p.bundles_index)) { p.bundles_index[b.id] = []; } p.bundles_index[b.id].push(b); })); nodes.forEach(n => { if (n.bundles_index ! == undefined) { n.bundles = Object.keys(n.bundles_index).map(k => n.bundles_index[k]); } else { n.bundles_index = {}; n.bundles = []; } n.bundles.forEach((b, i) => (b.i = i)); }); links.forEach(l => { if (l.bundle.links === undefined) { l.bundle.links = []; } l.bundle.links.push(l); }); // layout const padding = 8; const node_height = 22; const node_width = 70; const bundle_width = 40; const level_y_padding = 16; const metro_d = 4; const c = 16; const min_family_height = 16; nodes.forEach( n => (n.height = (Math.max(1, n.bundles.length) - 1) * metro_d) ); let x_offset = padding; let y_offset = padding; data.forEach(l => { x_offset += l.bundles.length * bundle_width; y_offset += level_y_padding; l.forEach((n, i) => { n.x = n.level * node_width + x_offset; n.y = node_height + y_offset + n.height / 2; y_offset += node_height + n.height; }); }); let i = 0; data.forEach(l => { l.bundles.forEach(b => { b.x = b.parents[0].x + node_width + (l.bundles.length - 1 - b.i) * bundle_width; b.y = i * node_height; }); i += l.length; }); links.forEach(l => { l.xt = l.target.x; l.yt = l.target.y + l.target.bundles_index[l.bundle.id].i * metro_d - (l.target.bundles.length * metro_d) / 2 + metro_d / 2; l.xb = l.bundle.x; l.xs = l.source.x; l.ys = l.source.y; }); // compress vertical space let y_negative_offset = 0; data.forEach(l => { y_negative_offset += -min_family_height + d3.min(l.bundles, b => d3.min(b.links, link => link.ys - c - (link.yt + c)) ) || 0; l.forEach(n => (n.y -= y_negative_offset)); }); // very ugly, I know links.forEach(l => { l.yt = l.target.y + l.target.bundles_index[l.bundle.id].i * metro_d - (l.target.bundles.length * metro_d) / 2 + metro_d / 2; l.ys = l.source.y; l.c1 = l.source.level - l.target.level > 1 ? node_width + c : c; l.c2 = c; }); const layout = { height: d3.max(nodes, n => n.y) + node_height / 2 + 2 * padding, node_height, node_width, bundle_width, level_y_padding, metro_d }; return { data, nodes, nodes_index, links, bundles, layout }; }Copy the code