gpt4 book ai didi

javascript - D3 V4 : Updated data is being seen as new data?(更新功能)

转载 作者:太空狗 更新时间:2023-10-29 14:18:39 27 4
gpt4 key购买 nike

目前正在搭建系统,更新功能遇到了一些问题。

本质上,我正在尝试向 D3 树添加新节点。当用户点击一个节点的“添加按钮”时,可以添加一个新的子节点。每个添加按钮都可以在每个节点的左侧找到。

我关注了 Mike Bostock 的 general update pattern .单击按钮后,唯一的"new"数据元素应该是新创建的子节点,但看起来整个数据都被视为"new"。当我查看每个节点的类名时,我得出了这个结论,并且所有节点都有一个过渡到中心节点并消失的明显事实。其他原始数据应该“更新”,但事实并非如此。任何人都可以轻轻地指出为什么会这样吗?

可以在这个 jfiddle link 中找到我的代码的工作示例.

编辑 06/09

根据 Gordon 的建议,我为我的节点和链接找到了一个唯一字段。因此,为了唯一标识数据,我做了以下更改:

节点

.data(d, d => d.data.name)

链接

.data(d, d => d.source.data.name)

此更改(大部分)有效,但我看到一些奇怪的行为仍在发生:(1) Branch 7.2.1 仍被识别为新节点并消失; (2) 在第二次“添加”左右之后,链接没有与它们各自的节点正确对齐。我认为我的两个小编辑正在影响这一点,因为当我回到原始代码时,线条被正确绘制,尽管它们正在过渡。想法?有什么建议吗?


HTML

  <div id="div-mindMap">

CSS

.linkMindMap {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
}
rect {
fill: white;
stroke: #3182bd;
stroke-width: 1.5px;
}

JS

const widthMindMap = 700;
const heightMindMap = 700;
let parsedData;

let parsedList = {
"name": " Stapler",
"children": [{
"name": " Bind",
"children": []
},
{
"name": " Nail",
"children": []
},
{
"name": " String",
"children": []
},
{
"name": " Glue",
"children": [{
"name": "Gum",
"children": []
},
{
"name": "Sticky Gum",
"children": []
}
]
},
{
"name": " Branch 3",
"children": []
},
{
"name": " Branch 4",
"children": [{
"name": " Branch 4.1",
"children": []
},
{
"name": " Branch 4.2",
"children": []
},
{
"name": " Branch 4.1",
"children": []
}
]
},
{
"name": " Branch 5",
"children": []
},
{
"name": " Branch 6",
"children": []
},
{
"name": " Branch 7",
"children": []
},
{
"name": " Branch 7.1",
"children": []
},
{
"name": " Branch 7.2",
"children": [{
"name": " Branch 7.2.1",
"children": []
},
{
"name": " Branch 7.2.1",
"children": []
}
]
}
]
}


let svgMindMap = d3.select('#div-mindMap')
.append("svg")
.attr("id", "svg-mindMap")
.attr("width", widthMindMap)
.attr("height", heightMindMap);


let backgroundLayer = svgMindMap.append('g')
.attr("width", widthMindMap)
.attr("height", heightMindMap)
.attr("class", "background")

let gLeft = backgroundLayer.append("g")
.attr("transform", "translate(" + widthMindMap / 2 + ",0)")
.attr("class", "g-left");
let gLeftLink = gLeft.append('g')
.attr('class', 'g-left-link');
let gLeftNode = gLeft.append('g')
.attr('class', 'g-left-node');


function loadMindMap(parsed) {
var data = parsed;
var split_index = Math.round(data.children.length / 2);

parsedData = {
"name": data.name,
"children": JSON.parse(JSON.stringify(data.children.slice(split_index)))
};

var left = d3.hierarchy(parsedData, d => d.children);

drawLeft(left, "left");
}

// draw single tree
function drawLeft(root, pos) {
var SWITCH_CONST = 1;
if (pos === "left") SWITCH_CONST = -1;

update(root, SWITCH_CONST);
}

function update(source, SWITCH_CONST) {
var tree = d3.tree()
.size([heightMindMap, SWITCH_CONST * (widthMindMap - 150) / 2]);
var root = tree(source);

console.log(root)

var nodes = root.descendants();
var links = root.links();

console.log(nodes)
console.log(links)
// Set both root nodes to be dead center vertically
nodes[0].x = heightMindMap / 2

//JOIN new data with old elements
var link = gLeftLink.selectAll(".link-left")
.data(links, d => d)
.style('stroke-width', 1.5);

var linkEnter = link.enter().append("path")
.attr("class", "linkMindMap link-left")
.attr("d", d3.linkHorizontal()
.x(d => d.y)
.y(d => d.x));

var linkUpdate = linkEnter.merge(link);

linkUpdate.transition()
.duration(750)
var linkExit = link.exit()
.transition()
.duration(750)
.attr('x1', function(d) {
return root.x;
})
.attr('y1', function(d) {
return root.y;
})
.attr('x2', function(d) {
return root.x;
})
.attr('y2', function(d) {
return root.y;
})
.remove();

//JOIN new data with old elements
var node = gLeftNode.selectAll(".nodeMindMap-left")
.data(nodes, d => d);

console.log(nodes);


//ENTER new elements present in new data
var nodeEnter = node.enter().append("g").merge(node)
.attr("class", function(d) {
return "nodeMindMap-left " + "nodeMindMap" + (d.children ? " node--internal" : " node--leaf");
})
.classed("enter", true)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
})
.attr("id", function(d) {
let str = d.data.name;
str = str.replace(/\s/g, '');
return str;
});

nodeEnter.append("circle")
.attr("r", function(d, i) {
return 2.5
});

var addLeftChild = nodeEnter.append("g")
.attr("class", "addHandler")
.attr("id", d => {
let str = d.data.name;
str = "addHandler-" + str.replace(/\s/g, '');
return str;
})
.style("opacity", "1")
.on("click", (d, i, nodes) => addNewLeftChild(d, i, nodes));

addLeftChild.append("line")
.attr("x1", -74)
.attr("y1", 1)
.attr("x2", -50)
.attr("y2", 1)
.attr("stroke", "#85e0e0")
.style("stroke-width", "2");

addLeftChild.append("rect")
.attr("x", "-77")
.attr("y", "-7")
.attr("height", 15)
.attr("width", 15)
.attr("rx", 5)
.attr("ry", 5)
.style("stroke", "#444")
.style("stroke-width", "1")
.style("fill", "#ccc");

addLeftChild.append("line")
.attr("x1", -74)
.attr("y1", 1)
.attr("x2", -65)
.attr("y2", 1)
.attr("stroke", "#444")
.style("stroke-width", "1.5");

addLeftChild.append("line")
.attr("x1", -69.5)
.attr("y1", -3)
.attr("x2", -69.5)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "1.5");

// .call(d3.drag().on("drag", dragged));;

nodeEnter.append("foreignObject")
.style("fill", "blue")
.attr("x", -50)
.attr("y", -7)
.attr("height", "20px")
.attr("width", "100px")
.append('xhtml:div')
.append('div')
.attr("class", 'clickable-node')
.attr("id", function(d) {
let str = d.data.name;
str = "div-" + str.replace(/\s/g, '');
return str;
})
.attr("ondblclick", "this.contentEditable=true")
.attr("onblur", "this.contentEditable=false")
.attr("contentEditable", "false")
.style("text-align", "center")
.text(d => d.data.name);

//TODO: make it dynamic
nodeEnter.insert("rect", "foreignObject")
.attr("ry", 6)
.attr("rx", 6)
.attr("y", -10)
.attr("height", 20)
.attr("width", 100)
// .filter(function(d) { return d.flipped; })
.attr("x", -50)
.classed("selected", false)
.attr("id", function(d) {
let str = d.data.name;
str = "rect-" + str.replace(/\s/g, '');
return str;
});

var nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate.transition()
.duration(750)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});

// Remove any exiting nodes
var nodeExit = node.exit()
.transition()
.duration(750)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();

// On exit reduce the node circles size to 0
nodeExit.select('circle').attr('r', 0);
// node = nodeEnter.merge(node)
}

function addNewLeftChild(d, i, nodes) {
console.log("make new child");
event.stopPropagation();
var newNodeObj = {
// name: new Date().getTime(),
name: "New Child",
children: []
};

console.log("this is ", parsedData)
//Creates new Node
var newNode = d3.hierarchy(newNodeObj);
newNode.depth = d.depth + 1;
newNode.height = d.height - 1;
newNode.parent = d;
newNode.id = Date.now();

console.log(newNode);
console.log(d)

if (d.data.children.length == 0) {
console.log("i have no children")
d.children = []
}
d.children.push(newNode)
d.data.children.push(newNode.data)

console.log(d)
let foo = d3.hierarchy(parsedData, d => d.children)
drawLeft(foo, "left");
}


loadMindMap(parsedList);

最佳答案

有几件事正在发生:

使用唯一键

使用名称不是键的最佳选择,因为每个新节点都有相同的名称(“新子节点”)。相反,最好使用某种 ID 系统。这是一个使用 ID 标记每个节点的数据的快速函数。

let currNodeId = 0;
function idData(node) {
node.nodeId = ++currNodeId;
node.children.forEach(idData);
}
idData(parsedList);

并且由于您正在重新定义 parsedData 中的数据,因此您也需要在那里使用 id 属性:

  parsedData = {
"name": data.name,
"nodeId": data.nodeId,
"children": JSON.parse(JSON.stringify(data.children.slice(split_index)))
};

添加新节点时,也可以在nodeData中设置:

  var newNodeObj = {
// name: new Date().getTime(),
name: "New Child",
nodeId: ++currNodeId,
children: []
};

然后实际使用 .nodeId 作为节点的键,将它用作键函数:

    .data(nodes, d => d.data.nodeId);

对于链接,您应该使用 target 而不是 source,因为这是一棵树并且每个 child 只有一个链接(而不是一个有多个链接 parent )。

    .data(nodes, d => d.target.data.nodeId);

防止添加多个节点元素

还有一个问题是在添加新元素之前合并新旧节点。为防止这种情况,您应该更改

node.enter().append("g").merge(node)

到:

node.enter().append("g")

链接转换

最后,您的链接的转换不会随节点一起转换。要使它们过渡,请移动:

    .attr("d", d3.linkHorizontal()
.x(d => d.y)
.y(d => d.x));

在下面

  linkUpdate.transition()
.duration(750)

所有内容看起来像这样:https://jsfiddle.net/v9wyb6q4/

或者:

const widthMindMap = 700;
const heightMindMap = 700;
let parsedData;

let parsedList = {
"name": " Stapler",
"children": [{
"name": " Bind",
"children": []
},
{
"name": " Nail",
"children": []
},
{
"name": " String",
"children": []
},
{
"name": " Glue",
"children": [{
"name": "Gum",
"children": []
},
{
"name": "Sticky Gum",
"children": []
}
]
},
{
"name": " Branch 3",
"children": []
},
{
"name": " Branch 4",
"children": [{
"name": " Branch 4.1",
"children": []
},
{
"name": " Branch 4.2",
"children": []
},
{
"name": " Branch 4.1",
"children": []
}
]
},
{
"name": " Branch 5",
"children": []
},
{
"name": " Branch 6",
"children": []
},
{
"name": " Branch 7",
"children": []
},
{
"name": " Branch 7.1",
"children": []
},
{
"name": " Branch 7.2",
"children": [{
"name": " Branch 7.2.1",
"children": []
},
{
"name": " Branch 7.2.1",
"children": []
}
]
}
]
}
let currNodeId = 0;
function idData(node) {
node.nodeId = ++currNodeId;
node.children.forEach(idData);
}
idData(parsedList);

let svgMindMap = d3.select('#div-mindMap')
.append("svg")
.attr("id", "svg-mindMap")
.attr("width", widthMindMap)
.attr("height", heightMindMap);


let backgroundLayer = svgMindMap.append('g')
.attr("width", widthMindMap)
.attr("height", heightMindMap)
.attr("class", "background")

let gLeft = backgroundLayer.append("g")
.attr("transform", "translate(" + widthMindMap / 2 + ",0)")
.attr("class", "g-left");
let gLeftLink = gLeft.append('g')
.attr('class', 'g-left-link');
let gLeftNode = gLeft.append('g')
.attr('class', 'g-left-node');


function loadMindMap(parsed) {
var data = parsed;
var split_index = Math.round(data.children.length / 2);

parsedData = {
"name": data.name,
"nodeId": data.nodeId,
"children": JSON.parse(JSON.stringify(data.children.slice(split_index)))
};

var left = d3.hierarchy(parsedData, d => d.children);

drawLeft(left, "left");
}

// draw single tree
function drawLeft(root, pos) {
var SWITCH_CONST = 1;
if (pos === "left") SWITCH_CONST = -1;

update(root, SWITCH_CONST);
}

function update(source, SWITCH_CONST) {
var tree = d3.tree()
.size([heightMindMap, SWITCH_CONST * (widthMindMap - 150) / 2]);
var root = tree(source);

console.log(root)

var nodes = root.descendants();
var links = root.links();

console.log(nodes)
console.log(links)
// Set both root nodes to be dead center vertically
nodes[0].x = heightMindMap / 2

//JOIN new data with old elements
var link = gLeftLink.selectAll(".link-left")
.data(links, d => d.target.data.nodeId)
.style('stroke-width', 1.5);

var linkEnter = link.enter().append("path")
.attr("class", "linkMindMap link-left");

var linkUpdate = linkEnter.merge(link);

linkUpdate.transition()
.duration(750)
.attr("d", d3.linkHorizontal()
.x(d => d.y)
.y(d => d.x));
var linkExit = link.exit()
.transition()
.duration(750)
.attr('x1', function(d) {
return root.x;
})
.attr('y1', function(d) {
return root.y;
})
.attr('x2', function(d) {
return root.x;
})
.attr('y2', function(d) {
return root.y;
})
.remove();

//JOIN new data with old elements
var node = gLeftNode.selectAll(".nodeMindMap-left")
.data(nodes, d => d.data.nodeId);

console.log(nodes);


//ENTER new elements present in new data
var nodeEnter = node.enter().append("g")
.attr("class", function(d) {
return "nodeMindMap-left " + "nodeMindMap" + (d.children ? " node--internal" : " node--leaf");
})
.classed("enter", true)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
})
.attr("id", function(d) {
let str = d.data.name;
str = str.replace(/\s/g, '');
return str;
});

nodeEnter.append("circle")
.attr("r", function(d, i) {
return 2.5
});

var addLeftChild = nodeEnter.append("g")
.attr("class", "addHandler")
.attr("id", d => {
let str = d.data.name;
str = "addHandler-" + str.replace(/\s/g, '');
return str;
})
.style("opacity", "1")
.on("click", (d, i, nodes) => addNewLeftChild(d, i, nodes));

addLeftChild.append("line")
.attr("x1", -74)
.attr("y1", 1)
.attr("x2", -50)
.attr("y2", 1)
.attr("stroke", "#85e0e0")
.style("stroke-width", "2");

addLeftChild.append("rect")
.attr("x", "-77")
.attr("y", "-7")
.attr("height", 15)
.attr("width", 15)
.attr("rx", 5)
.attr("ry", 5)
.style("stroke", "#444")
.style("stroke-width", "1")
.style("fill", "#ccc");

addLeftChild.append("line")
.attr("x1", -74)
.attr("y1", 1)
.attr("x2", -65)
.attr("y2", 1)
.attr("stroke", "#444")
.style("stroke-width", "1.5");

addLeftChild.append("line")
.attr("x1", -69.5)
.attr("y1", -3)
.attr("x2", -69.5)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "1.5");

// .call(d3.drag().on("drag", dragged));;

nodeEnter.append("foreignObject")
.style("fill", "blue")
.attr("x", -50)
.attr("y", -7)
.attr("height", "20px")
.attr("width", "100px")
.append('xhtml:div')
.append('div')
.attr("class", 'clickable-node')
.attr("id", function(d) {
let str = d.data.name;
str = "div-" + str.replace(/\s/g, '');
return str;
})
.attr("ondblclick", "this.contentEditable=true")
.attr("onblur", "this.contentEditable=false")
.attr("contentEditable", "false")
.style("text-align", "center")
.text(d => d.data.name);

//TODO: make it dynamic
nodeEnter.insert("rect", "foreignObject")
.attr("ry", 6)
.attr("rx", 6)
.attr("y", -10)
.attr("height", 20)
.attr("width", 100)
// .filter(function(d) { return d.flipped; })
.attr("x", -50)
.classed("selected", false)
.attr("id", function(d) {
let str = d.data.name;
str = "rect-" + str.replace(/\s/g, '');
return str;
});

var nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate.transition()
.duration(750)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});

// Remove any exiting nodes
var nodeExit = node.exit()
.transition()
.duration(750)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();

// On exit reduce the node circles size to 0
nodeExit.select('circle').attr('r', 0);
// node = nodeEnter.merge(node)
}

function addNewLeftChild(d, i, nodes) {
console.log("make new child");
event.stopPropagation();
var newNodeObj = {
// name: new Date().getTime(),
name: "New Child",
nodeId: ++currNodeId,
children: []
};

console.log("this is ", parsedData)
//Creates new Node
var newNode = d3.hierarchy(newNodeObj);
newNode.depth = d.depth + 1;
newNode.height = d.height - 1;
newNode.parent = d;
newNode.id = Date.now();

console.log(newNode);
console.log(d)

if (d.data.children.length == 0) {
console.log("i have no children")
d.children = []
}
d.children.push(newNode)
d.data.children.push(newNode.data)

console.log(d)
let foo = d3.hierarchy(parsedData, d => d.children)
drawLeft(foo, "left");
}


loadMindMap(parsedList);
.linkMindMap {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
}
rect {
fill: white;
stroke: #3182bd;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="div-mindMap">

关于javascript - D3 V4 : Updated data is being seen as new data?(更新功能),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56510419/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com