Author: BP-Captain

Source:Juejin. Cn/post / 684490…

What is D3js?

  • Is a data visualization library that can create beautiful and complex charts.
  • Is a data-driven data visualization library that binds data to the DOM before it can be displayed.
  • Is a JAVASCRIPT data visualization library based on Html, CSS, SVG/Canvas.

Effect of this code:

  • There are arrows on the line
  • Click the node to change the color of the node and the thickness of the connecting line
  • Can zoom
  • Able to drag and drop.

Version: 4. X

Installation and import

NPM install: NPM install d3

Import * as d3 from ‘d3’;

I. Complete code

import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { push } from 'react-router-redux'; import * as d3 from 'd3'; import { Row, Form } from 'antd'; import { chartReq} from './actionCreator'; import './Chart.less'; const WIDTH = 1900; const HEIGHT = 580; const R = 30; let simulation; class Chart extends Component { constructor(props, context) { super(props, context); this.print = this.print.bind(this); this.forceChart = this.forceChart.bind(this); this.state = { }; } componentWillMount() { this.props.dispatch(push('/Chart')); } componentDidMount() { this.print(); } print() {let callback = (res) => {// State let nodeData = res.data.nodes; let relationData = res.data.rels; this.setState({ nodeData: res.data.nodes, relationData: res.data.rels, }); let nodes = []; for (let i = 0; i < nodeData.length; i++) { nodes.push({ id: (nodeData[i] && nodeData[i].id) || '', name: (nodeData[i] && nodeData[i].name) || '', type: (nodeData[i] && nodeData[i].type) || '', definition: (nodeData[i] && nodeData[i].definition) || '', }); } let edges = []; for (let i = 0; i < relationData.length; i++) { edges.push({ id: (relationData[i] && (relationData[i].id)) || '', source: (relationData[i] && relationData[i].start.id) || '', target: (relationData[i] && relationData[i].end.id) || '', tag: (relationData[i] && relationData[i].name) || '', }); } this.forceChart(nodes, edges); // d3 force guide chart contents}; this.props.dispatch(chartReq({ param: param }, callback)); } // func forceChart(nodes, edges) { this.refs['theChart'].innerHTML = ''; **} render() {return (<Row style={{minWidth: 900 }}> <div className="outerDiv"> <div className="theChart" id="theChart" ref="theChart"> </div> </div> </Row> ); } } Chart.propTypes = { dispatch: PropTypes.func.isRequired, }; function mapStateToProps(state) { return { }; } const WrappedChart = Form.create({})(Chart); export default connect(mapStateToProps)(WrappedChart);Copy the code

Break down the code

1. The components

<div className="theChart" id="theChart" ref="theChart">
</div>
Copy the code

The entire diagram will be drawn inside the div.

2. Construct nodes and cables

Nodes and wires have been constructed in the full code, but that is based on data coming in from the background and may not be intuitive. Now I give you two sets of data, and then I construct the data for the nodes and wires.

Const nodeData = [{id: 1, name: 'China'}, {id: 2, name: 'Beijing'}, {id: 3, name: 'tianjin'}, {id: 4, name: "Shanghai"}, {id: 5, name: 'chongqing'}, {id: 6, name: 'fujian'}, {id: 7, name: 'guangdong'}, {id: 8, name: 'guangxi'}, {id: 9, name: 'zhejiang'}, {id: 10, name: 'jiangsu'}, {id: 11, name: 'hebei'}, {id: 12, name: 'the shanxi'}, {id: 13, name: 'jilin'}, {id: 14, name: 'liaoning'}, {id: 15, name: 'heilongjiang'}, {id: 16, name: 'anhui'}, {id: 17, name: 'jiangxi'}, {id: 18, name: 'shandong'}, {id: 19, name: "Henan"}, {id: 20, name: 'hunan'}, {id: 21, name: 'hubei'}, {id: 22, name: 'hainan'}, {id: 23, name: 'guizhou'}, {id: 24, name: "Yunnan"}, {id: 25, name: 'xinjiang'}, {id: 26, name: 'Tibet'}, {id: 27, name: 'Taiwan'}, {id: 28, name: 'the'}, {id: 29, name: 'Hong Kong'}, {id: 30, name: 'shaanxi'}, {id: 31, name: 'gansu'}, {id: 32, name: 'qinghai'}, {id: 33, name: 'Inner Mongolia'}, {id: 34, name: 'ningxia'}, {id: 35, name: 'sichuan'}, {id: 36, name: 'fuzhou}, {id: 37, name:' xiamen '}, {id: 38, name: 'zhangzhou'}, {id: 39, name: 'putian'}, {id: 40, name: 'nanping}, {id: 41, name:' longyan '}, {id: 42, name: 'sanming'}, {id: 43, name: 'ningde}, {id: 44, name: 'Quanzhou'},]; let nodes = []; for (let i = 0; i < nodeData.length; I++) {nodes. Push ({id: (nodeData [I] && nodeData [I] id) | | ', / / the node id name: (nodeData [I] && nodeData [I] name) | | ', / / node name}); } const relData = [{id: 1, source: 1, target: 2, tag: 'province'}, {id: 2, source: 1, target: 3, tag: 'province'}, {id: 1, target: 3, tag: 'province'}, 3, the source: 1, target: 4, tag: 'provinces'}, {id: 4, source: 1, target: 5, the tag:' provinces'}, {id: 5, source: 1, target: 6, the tag: 'province'}, {id: 6, source: 6, target: 36, tag: 'prefecture'}, {id: 6, target: 37, tag: 'prefecture'}, {id: 8, source: 6, target: 38, tag: '-tag'}, {id: 9, source: 6, target: 39, tag: '-tag'}, {id: 10, source: 6, target: 40, tag: '-tag'}, {id: 10, source: 6, target: 40, tag: '-tag'}, }, {id: 11, source: 6, target: 42, tag: 'iD'}, {id: 11, source: 6, target: 42, tag: 'ID'}, {id: 12, target: 42, tag: 'ID'}, 13, source: 6, target: 43, tag: 'cz'}, {id: 6, target: 44, tag: 'CZ'}, {id: 15, source: 1, target: 1 7, the tag: 'provinces'}, {id: 16, source: 1, target: 8, the tag:' provinces'}, {id: 17, source: 1, target: 9, tag: 'provinces'}, {id: 18, source: 1, target: 44, tag: 'province'}, {id: 19, source: 1, target: 10, tag: 'province'}, {id: 20, source: 1, target: 1, target: 1, tag: 'province'}, {id: 20, source: 1, target: 1 11, the tag: 'provinces'}, {id: 21, source: 1, target: 12, the tag:' provinces'}, {id: 22, source: 1, target: 13, tag: 'provinces'}, {id: 23, source: 1, target: 14, tag: 'province'}, {id: 1, target: 15, tag: 'province'}, {id: 25, source: 1, target: 1, target: 1 16, the tag: 'provinces'}, {id: 26, source: 1, target: 17, tag:' provinces'}, {id: 27, source: 1, target: 18, tag: 'provinces'}, {id: 28, source: 1, target: 19, tag: 'province'}, {id: 1, target: 20, tag: 'province'}, {id: 23, source: 1, target: 1 21, tag: 'province'}, {id: 31 source: 1, target: 22, tag: 'province'}, {id: 32, target: 23, tag: 'province'}, {id: 31 33, source: 1, target: 24, tag: 'province'}, {id: 35, source: 1, target: 24, tag: 'province'}, {id: 35, source: 1, target: 24, tag: 'province'}, {id: 35, source: 1, target: 1 26, the tag: 'provinces'}, {id: 36, source: 1, target: 27, tag:' provinces'}, {id: 37, source: 1, target: 28, tag: 'provinces'}, {id: 38, source: 1, target: 29, tag: 'province'}, {id: 1, target: 30, tag: 'province'}, {id: 40, source: 1, target: 1, target: 1 31, tag: 'province'}, {id: 41, source: 1, target: 32, tag: 'province'}, {id: 42, source: 1, target: 33, tag: 'province'}, {id: 40, target: 33, tag: 'province'}, {id: 40, target: 33, tag: 'province'}, {id: 40, target: 33, tag: 'province'}, {id: 40, target: 32, tag: 'province'}, 40, source: 1, target: 34, tag: 'province'},]; let edges = []; for (let i = 0; i < relData.length; I++) {edges. Push ({id: (relData [I] && (relData [I] id)) | | ', / / connection id source: relData [I]. The source and target / / node: RelData [I]. Target, / / the end node tag: (relData [I] tag) | | ', / / attachment name}); }Copy the code

Depending on your project, give the force guide diagram whatever it needs.

3. Define the force model

Const simulation = d3.forceSimulation(nodes) // Specify the array to be referenced. Force ('link', D. ForceLink (edges).id(d => d. ID).distance(150)).force(' Collision ', d3.forcecollide (1).strength(0.1)).force('center', d3.forceCenter(WIDTH / 2, HEIGHT / 2)) .force('charge', d3.forceManyBody().strength(-1000).distanceMax(800));Copy the code

Using simulation. Force () to set the force, you can set any of these forces:

  • Center: gravity point. Set the Center of gravity in the force steering diagram. After setting, no matter how to drag, the center of gravity of the force will not change; If you don’t set it, the center of gravity will change, but the initial position of the center of gravity will be at the origin, which means that when you enter the page, you will only see a quarter of the image, which affects the experience.

  • Collision: node Collision force,. Strength parameter range is [0,1].

  • Links: the force of connection; . Distance Sets the distance between nodes on both ends of the cable.

  • Many-body:. When strength is positive, it simulates gravity; when strength is negative, it simulates charge force; . DistanceMax Sets the maximum distance.

  • Positioning: A force that gives direction.

Monitor the position changes of force elements by simulation. On. (Please refer to “Listening for position changes of graph elements” below.)

4. Drawing SVG

Const SVG = d3.select('#theChart').append(' SVG ') // Create svG. style('width', width).style('height', 'height') HEIGHT * 0.9). On ('click', () => {console.log('click', d3.event.target.tagname); }) .call(zoom); // Zoom const g = svg.append('g'); // create g in SVGCopy the code

Create SVG, create G in SVG, put the node wiring and so on in G.

  • Select: selects the first corresponding element

  • SelectAll: Selects all corresponding elements

  • Append: Creates elements

  • Style: Sets the style

  • On (‘click’, function()) : click sets the click response event

  • Call (Zoom) : The zoom function, see the “zoom” section below for details

5. Draw cables

Select ('g').selectAll('line').data(edges) const edgesLine = svg.select('g').selectAll('line').data(edges) // Bind the data.enter () // Add the corresponding number of placeholders to the data.append ('path') // Generates polylines over placeholders (drawn with path).attr('d', (d) => { return d && 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y; }) // Iterate over all data. Attr ('id', (d, I) => {return I && 'edgePath' + I; attr('id', (d, I) => {return I && 'edgePath' + I; }) // set id, Attr ('marker-end', 'url(#arrow)') // Mark arrows according to the id of the arrow marker. Style ('stroke', '#000') // color. Style ('stroke-width', 1); / / the thicknessCopy the code
  • Data (), Enter (),append() : These three work together, binding data to create the graph

  • Attr: Sets properties

  • Style: Sets the style

  • The lines are plotted using bezier curves :(M starting point X starting point y L ending point X ending point y)

If you want to learn more about Bezier curves, go to: click I know Bezier curves.

6. Line name

Const edgesText = svg.select('g').selectAll('.edgelabel').data(edges).enter().append('text') // Create a text area for each line .attr('class', 'edgelabel') .attr('dx', 80) .attr('dy', 0); edgesText.append('textPath') .attr('xlink:href', (d, i) => { return i && '#edgepath' + i; Style ('pointer-events', 'none') // Disable mouse events.text ((d) => {return d&&d.tag; }); // Set the text contentCopy the code
  • Attr () is placed after.append() and represents the setting property for the element created by.append()
  • .style(‘pointer-events’, ‘none’) Disables mouse events: cannot be selected, cannot be clicked, and does not become a vertical bar when the mouse is over it.

7. Draw the arrows on the lines

const defs = g.append('defs'); Const arrowheads = defs.append('marker') // Create arrow.attr ('id', 'arrow') //.attr('markerUnits', 'strokeWidth') // Arrow set to strokeWidth scales with line thickness. Attr ('markerUnits', Attr ('class', 'arrowhead').attr('markerWidth', 20) // viewport .attr('markerHeight', 20) // viewport .attr('viewBox', '0 0 20 20') // viewBox .attr('refX', 9.3 + R) / / deviation distance. Attr (' refY ', 5) / / deviation distance. Attr (' received ', 'auto'); Arrowheads.append ('path').attr('d', 'M0,0 L0,10 L10,5 z') // d: Attr ('fill', '#000'); // Fill the colorCopy the code
  • Viewport: the viewport area
  • ViewBox: The actual size, which is automatically scaled until the viewPort is filled

On the relationship of the viewport and viewBox still don’t understand, please click: understand the viewport of SVG viewBox, preserveAspectRatio.

8. Draw nodes

Const nodesCircle = svg.select('g').selectAll('circle').data(Nodes).enter().append('circle') // Create circle.attr('r', 30) / / radius. Style (' fill ', '# 9 (ff) / / fill color. Style (the' stroke ', '# 0 cf) / / border color, style (' stroke - width, (2) / / border thickness) on (' click '(node) = > {/ / click event console. The log (" click "); }) .call(drag); // Drag a single node to drive the entire graphCopy the code

Create circles as nodes.

.call() calls the drag function.

9. Node name

const nodesTexts = svg.select('g') .selectAll('text') .data(nodes) .enter() .append('text') .attr('dy', '.3em') // offset.attr ('text-anchor', 'middle') // node name placed in middle of circle. Style ('fill', 'black') // color. style('pointer-events', 'none') // Disable mouse events.text ((d) => {// Text content return d&&d.name; // Iterate over nodes to get the corresponding name});Copy the code

The significance of banning mouse events here is twofold:

  • Mouse over the text will not become a vertical bar, or keep the appearance of the arrow, so the experience is good.

  • We set the node to click events, and if we don’t disable mouse events, the text takes up space, and at the top of the node, we can’t respond to the node events when we click on the text.

10. A bubble is displayed when you move the pointer to a node

NodesCircle. Append ('title').text((node) => {//.text sets the bubble prompt content return node.name; });Copy the code

11. Listen for position changes of graph elements

Simulation. On ('tick', () => {nodesCircle ('transform', (d) => { return d && 'translate(' + d.x + ',' + d.y + ')'; }); / / update text node coordinates nodesTexts. Attr (' transform ', (d) = > {return 'translate (' + (d.x) +', '+ d.y +') '. }); Edgesline.attr ('d', (d) => { const path = 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y; return path; }); EdgesText. Attr ('transform', (d, I) => {return 'rotate(0)'; }); });Copy the code

12. Drag and drop

function onDragStart(d) { // console.log('start'); // console.log(d3.event.active); if (! D3.event.active) {simulation. AlphaTarget (1) // Set the attenuation coefficient to simulate the moving process of node position. The higher the value, the faster the moving process. // 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; } function dragging(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function onDragEnd(d) { if (! d3.event.active) simulation.alphaTarget(0); d.fx = null; // Remove the fixed coordinate in the partying state. } const drag = d3.drag().on('start', onDragStart).on('drag', dragging) // Dragging process.on('end', onDragEnd);Copy the code

13. The zoom

function onZoomStart(d) { // console.log('start zoom'); } function zooming(d) {// zoom and drag the entire g // console.log('zoom ing', d3.event.transform, d3.zoomTransform(this)); g.attr('transform', d3.event.transform); // Get the scaling factor and coordinate of g. } function onZoomEnd() { // console.log('zoom end'); } const zoom = d3. Zoom () / /. TranslateExtent ([[0, 0], [WIDTH, HEIGHT]]) / / set or obtain translation interval, the default is [[- up - up], [+ up. On ('zoom', zooming). On ('end', onZoomEnd); on('end', onZoomEnd); on('end', onZoomEnd);Copy the code

Other effects

1. When you click a node, make the connection line bold

nodesCircle.on('click, (node) => {
    edges_line.style("stroke-width",function(line){
        if(line.source.name==node.name || line.target.name==node.name){
            return 4;
        }else{
            return 0.5;
        }
    });
})
Copy the code

2. The clicked node changes color

NodesCircle. On ('click, (node) => {nodesCircle. Style ('fill', (nodeOfSelected) => {// nodeOfSelected: If (nodeofselection. id === node.id) {// The selected node will change color console.log('node') return '#36F'; } else { return '#9FF'; }}); })Copy the code

By the way, in React, prevState is the value of the previous state. With State and prevState, you can make more judgments and create more variation.

Use react with caution

componentDidMount() { this.print(); } print() {let callback = (res) => {// State let nodeData = res.data.nodes; let relationData = res.data.rels; this.setState({ nodeData: res.data.nodes, relationData: res.data.rels, }); let nodes = []; for (let i = 0; i < nodeData.length; I++) {nodes. Push ({id: (nodeData [I] && nodeData [I] id) | | ', name: (nodeData [I] && nodeData [I] name) | | ', type: (nodeData[i] && nodeData[i].type) || '', definition: (nodeData[i] && nodeData[i].definition) || '', }); } let edges = []; for (let i = 0; i < relationData.length; i++) { edges.push({ id: (relationData[i] && (relationData[i].id)) || '', source: (relationData [I] && relationData [I] start. Id) | | ', target: (relationData [I] && relationData [I] end. Id) | | ', the tag: (relationData[i] && relationData[i].name) || '', }); } this.forceChart(nodes, edges); // d3 force guide chart contents}; this.props.dispatch(getDataFromNeo4J({ neo4jrun: 'match p=(()-[r]-()) return p limit 300', }, callback)); }Copy the code

Where is the diagram constructed?

Because the graph is dynamic, if the render is executed many times, it will not cover the previous rendering of the graph, but will cause the phenomenon of multiple renderings. Putting the constructor function print() inside componentDidMount() only renders once.

After adding, deleting and modifying nodes and wire data, you need to call print() again to reconstruct the graph.

Where to get the data?

The data is not retrieved from redux and is retrieved directly by callback after sending the request.

Five, dry goods: D3 project search website

If you want to find D3js instances, you can click here: D3js project Search.

Everybody big guy much advice!!

Please indicate the source in accordance with the format:

Author: BP-Captain

Source:Juejin. Cn/post / 684490…