我正在尝试创建一个动态d3.js力导向图表。我使用generateData函数创建一些数据,然后使用updateData每3秒更新一次这些数据。
初始数据显示良好,但随后我添加的新节点堆叠在SVG的左上方,链接完全消失。
希望有更多d3.js经验的人可以帮助我。请参阅下面的代码。
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.0/Chart.bundle.min.js"></script>
<canvas id="myChart" width="400" height="400"></canvas>
&#13;
let nodes = [];
let previousNodes = [];
let links = [];
let index = 0;
const familyTypes = ['Temperature', 'Energy', 'Thermostat', 'Humidity', 'Light']
generateData = (num) => {
const sensors = [];
for (let i = 0; i < num; i++) {
index++;
const sensor = {
id: (index).toString(),
familyType: familyTypes[Math.floor(Math.random() * 5)]
};
sensors.push(sensor);
}
return sensors;
}
updateData = (currentSensors) => {
const resultSensors = JSON.parse(JSON.stringify(currentSensors));
// Removes elements
resultSensors.splice(2, 5);
// Adds elements
const newSensors = generateData(5);
newSensors.map(sensor => resultSensors.push(sensor));
return resultSensors;
}
updateNode = (newNodes) => {
const resultNodes = JSON.parse(JSON.stringify(previousNodes));
const previousIds = previousNodes.map(node => node.id);
const ids = newNodes.map(node => node.id);
const idAdded = ids.filter(node => previousIds.indexOf(node) === -1);
const idRemoved = previousIds.filter(node => ids.indexOf(node) === -1);
const nodeRemoved = previousNodes.filter(previousNode => idRemoved.indexOf(previousNode.id) > -1);
const nodeAdded = newNodes.filter(node => idAdded.indexOf(node.id) > -1);
nodeRemoved.forEach(node => resultNodes.splice(previousNodes.indexOf(node), 1));
nodeAdded.forEach(node => resultNodes.push(node));
previousNodes = JSON.parse(JSON.stringify(newNodes));
return resultNodes;
}
computeLinks = (newNodes) => {
const groupedObject = _.groupBy(nodes, 'familyType');
newLinks = [];
Object.keys(groupedObject).map((key, i) => {
const sensorsByType = groupedObject[key];
for (let j = 0; j < sensorsByType.length; j++) {
if (j > 0) newLinks.push({ source: sensorsByType[j], target: sensorsByType[j - 1] });
}
});
return newLinks;
}
var width = window.innerWidth
var height = window.innerHeight
var color = d3.scaleOrdinal(d3.schemeCategory20);
var svg = d3.select('svg')
svg.attr('width', width).attr('height', height)
var linkElements,
nodeElements,
textElements
var linkGroup = svg.append('g').attr('class', 'links')
var nodeGroup = svg.append('g').attr('class', 'nodes')
var textGroup = svg.append('g').attr('class', 'texts')
var simulation = d3
.forceSimulation()
.force('link', d3.forceLink().id(d => d.id))
.force('charge', d3.forceManyBody())
.force('center', d3.forceCenter(width / 2, height / 2));
var data = generateData(20);
nodes = updateNode(data);
links = computeLinks(data);
updateGraph = () => {
// links
linkElements = linkGroup.selectAll('line')
.data(links, link => link.target.id + link.source.id)
linkElements.exit().remove()
var linkEnter = linkElements
.enter().append('line')
.attr('stroke-width', 1)
.attr('stroke', '#d2d7d3')
linkElements = linkEnter.merge(linkElements)
// nodes
nodeElements = nodeGroup.selectAll('circle')
.data(nodes, node => node.id)
nodeElements.exit().remove()
var nodeEnter = nodeElements
.enter()
.append('circle')
.attr('r', 5)
.attr('fill', node => color(node.familyType))
.call(d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended));
nodeElements = nodeEnter.merge(nodeElements)
// texts
textElements = textGroup.selectAll('text')
.data(nodes, node => node.id )
textElements.exit().remove()
var textEnter = textElements
.enter()
.append('text')
.text(node => node.familyType )
.attr('font-size', 15)
.attr('dx', 15)
.attr('dy', 4)
textElements = textEnter.merge(textElements)
}
dragstarted = (d) => {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
dragged = (d) => {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
dragended = (d) => {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
function updateSimulation() {
updateGraph()
simulation.nodes(nodes).on('tick', () => {
nodeElements
.attr('cx', function (node) { return node.x })
.attr('cy', function (node) { return node.y })
textElements
.attr('x', function (node) { return node.x })
.attr('y', function (node) { return node.y })
linkElements
.attr('x1', function (link) { return link.source.x })
.attr('y1', function (link) { return link.source.y })
.attr('x2', function (link) { return link.target.x })
.attr('y2', function (link) { return link.target.y })
})
simulation.force('link').links(links)
}
updateSimulation()
setInterval(() => {
data = updateData(data);
nodes = updateNode(data);
updateSimulation()
}, 3000)
&#13;