I make a React/Redux app with graph visualization using d3. I try to bind nodes and links in my graph without force simulation, but I can't get a path attribute (implementing link) in onDrag function, where I process a node dragging.
I'm new in d3 and may be it is a bad graph implementation. The most of node/link examples implemented with force, where the binding between nodes and links is implicit. I've found that and that, but it uses old d3 v3 and links.each(...) doesn't work in my app.
So, here is a code of my React component:
import React,{Component} from 'react';
import * as d3 from "d3";
class MapPanel extends Component {
constructor(props) {
super(props);
this.state = {
data: {
nodes: [
{id: 'A', x: 100, y: 100},
{id: 'B', x: 200, y: 100},
{id: 'C', x: 100, y: 200},
{id: 'D', x: 200, y: 200},
],
links: [
{ source: 'A', target: "B" },
{ source: 'A', target: "C" },
{ source: 'B', target: "C" },
{ source: 'C', target: "D" },
]
}
};
}
componentDidMount() {
const chart = d3.select(this.chartRef)
.attr('width', window.innerWidth-100)
.attr('height', 500)
.attr("id", "svg");
chart.append('defs').selectAll('marker')
.data(['end'])
.enter()
.append('marker')
.attr('id', "arrowhead")
.attr('refX', 22)
.attr('refY', 0)
.attr('orient', "auto")
.attr('markerWidth', 25)
.attr('markerHeight', 25)
.attr('markerUnits', "strokeWidth")
.attr('xoverflow', "visible")
.attr('viewBox', "0 -5 15 15")
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#f00');
const links = chart.selectAll('link')
.data(this.state.data.links);
links
.enter()
.append('path')
.attr('marker-end','url(#arrowhead)')
.attr("stroke", "black")
.attr('d', (d, i) => {
let sourceNode = this.state.data.nodes.find(node => node.id === d.source);
let targetNode = this.state.data.nodes.find(node => node.id === d.target);
return 'M ' + (sourceNode.x + 25) + ' ' + (sourceNode.y + 25) + ' L ' + (targetNode.x + 25) + ' ' + (targetNode.y + 25);
});
const rects = chart.selectAll('rect')
.data(this.state.data.nodes);
rects
.enter()
.append('rect')
.attr('x', (d, i) => d.x)
.attr('y', (d, i) => d.y)
.attr('width', 50)
.attr('height', 50)
.attr('rx', 10)
.attr('ry', 10)
.merge(rects) //merges enter() and update()
.call(
d3.drag()
.on('start', this.onDragStart)
.on('drag', this.onDrag(links))
.on('end', this.onDragEnd)
);
rects
.exit()
.transition()
.attr('r', 0)
.remove();
}
onDragStart() {
d3.select(this)
.raise() //move dragged element to last position in nodes list to layer element ontop other similar elements
.classed('active', true);
}
onDrag(links) {
return function (d, i) {
d3.select(this).attr("x", d.x = d3.event.x).attr("y", d.y = d3.event.y);
/*links.attr('d', function(link, linkIndex) {
if(link.source === d.id) return 'M 10 10 L 100 100';
});*/
}
}
onDragEnd() {
d3.select(this).classed('active', false);
}
render = () =>(
<svg className="cognitive-map" ref={(r) => this.chartRef = r}></svg>
);
}
export default MapPanel;