英文:
Proper method of chaining and selecting nested group elements for D3 updating?
问题
我遇到了一个问题,我的工具提示在鼠标进入选择时不会跟随鼠标位置。
附上了我的代码DOM结构的示意图,其中高亮部分表示我要选择和修改的部分。我在这里使用了element作为占位符。
在我的情况下,g.child
表示一个工具提示,它随着鼠标坐标 (x, y) 移动。我的主要问题是,在进入 g.child
时,它不按预期运行(即:不移动,保持在一个位置不变)。然而,在更新 g.child
和 element.child
之后,它会按预期运行(即:随着鼠标位置移动)。
我根据以下假设编写了这段代码,并欢迎输入,如果这些假设不正确:
selectAll(selector)
递归工作并扁平化选择,而select(selector)
选择第一个匹配项但向前传播数据。- 通过链式调用
svg.selectAll('g.parent).select('g.child').select('element.child')
,我将在选择最终子元素时保持层次结构,从而使我能够更新它。 - 由于数据绑定到了
parents
并由唯一标识符指定,我没有在变量链中的子元素下进一步使用data()
重新定义它,因为它绑定并通过parents
传播。 - 我不需要包括退出部分,因为我不会摆脱任何现有元素,只会更新已经进入的元素。
以下是我的问题的示例代码。我只包括了脚本的进入和更新部分:
// 虚拟数据
let data = [
{ key: 1,
light: '#ffffff',
dark: '#000000' },
...
, // 注意:n 表示第 n 个值
{ key: n,
light: '#eeeeee',
dark: '#222222' }
]
//=== D3 ===
const svg = d3.select('svg')
const parents = svg
.selectAll('g.parent')
.data(data, d => d.key) // 使用标识符维护对象的一致性
// 进入
const enterParents = parents
.enter()
.append('g')
.attr('class', 'parent')
.attr('width', 800)
.attr('height', 360)
enterParents
.append('element')
.attr('class', 'parent')
.attr('fill', d => d.light)
// 进入嵌套的子组 g.child per g.parent
const children = parents.select('g.child')
const enterChild = enterParents
.append('g')
.attr('class', 'child')
enterChild
.append('element')
.class('child')
.attr('fill', d => d.light)
// 更新
parents
.select('element.parent')
.attr('fill', d => d.dark)
children
.select('element.child')
.attr('fill', d => d.dark)
// 为鼠标位置悬停在任何 g.parent 上添加事件监听器
parents
.on('mousemove', function(e) {
const mouse = d3.pointer(e)
const mouseX = mouse[0]
const mouseY = mouse[1]
children
.attr('transform', `translate( ${mouseX}, ${mouseY})`)
})
工具提示背景更新如预期,但唯一的问题是工具提示在鼠标移动时不会跟随鼠标。在进入时它保持静态,但在更新后会随着鼠标移动。
我的问题是工具提示位置不随鼠标移动是否与如何选择、存储和调用组有关?
英文:
I am having issues with my tooltip not following my mouse position on only the enter selection.
Attached is a diagram of the DOM structure of my code, where the highlighted portions represent what I am trying to select and modify. I used element as a placeholder.
In my case, g.child
represents a tooltip that moves alongside (x, y) mouse co-ordinates. My main problem is that entering g.child
, it doesn't behave as expected (i.e.: doesn't move and remains static in one position). However after updating g.child
and element.child
behaves as expected (i.e.: moves alongside the mouse position).
I wrote this code under these assumptions and welcome input if they're incorrect:
selectAll(selector)
works recursively and flattens the selection, whereasselect(selector)
selects the first match but propagates the data forward.- by chaining
svg.selectAll('g.parent).select('g.child').select('element.child')
, I would be maintaining hierarchy while selecting the final child element, giving me the ability to update it. - since the data is bound
parents
and specified by a unique identifier, I did not re-define usingdata()
further down the variable chain to children elements as it's bound and propagated throughparents
. - I did not need to include an exit portion as I am not getting rid of any existing elements, only updating whatever is already entered.
Here is the adapted example code for my problem. I have only included the entering and updating bits of the script:
// dummy data
let data = [
{ key: 1,
light = #ffffff,
dark = #000000 },
...
, // note: n represents the nth value
{ key: n,
light = #eeeeee,
dark = #222222 }
]
//=== D3 ====
const svg = d3.select('svg')
const parents = svg
.selectAll('g.parent')
.data(data, d => d.key) // maintaining object constancy using identifier
// ENTER
const enterParents = parents
.enter()
.append('g')
.attr('class', 'parent')
.attr('width', 800)
.attr('height', 360)
enterParents
.append('element')
.attr('class', 'parent')
.attr('fill', d => d.light)
// entering nested child group g.child per g.parent
const children = parents.select('g.child')
enterChild = enterParents
.append('g')
.attr('class', 'child')
enterChild
.append('element')
.class('child')
.attr('fill', d => d.light)
// UPDATE
parents
.select('element.parent')
.attr('fill', d => d.dark)
children
.select('element.child')
.attr('fill', d => d.dark)
// adding event listener for mouse position hovering over any g.parent
parents
.on('mousemove', function(e) {
const mouse = d3.pointer(e)
const mouseX = mouse[0]
const mouseY = mouse[1]
children
.attr('transform', `translate( ${mouseX}, ${mouseY})`)
})
}
The tooltip background updates as expected, but my only issue is the tooltip not following the mouse on mousemove. It is static on entering but after updating it moves alongside the mouse.
Would my problem with the tooltip position not moving with the mouse be a result of how groups are selected, stored, and called as variables?
答案1
得分: 0
我弄清楚了,这个过程只涉及将合并的 enter + update 选择存储到一个变量中,以在代码中进一步调用。我问题中的代码只涉及跟踪更新选择上的鼠标位置,因此鼠标位置只在更新时表现如预期,而不在进入时表现如预期。
下面是修改后的代码,其中更新现在在代码的合并部分上执行。如果我想的话,可以通过在另一行上调用合并变量来分别处理更新和进入的操作。
// 虚拟数据
let data = [
{ key: 1,
light = #ffffff,
dark = #000000 },
...
, // 注意:n表示第n个值
{ key: n,
light = #eeeeee,
dark = #222222 }
]
//=== D3 ===
const svg = d3.select('svg')
const parents = svg
.selectAll('g.parent')
.data(data, d => d.key) // 使用标识符维护对象一致性
// 进入
const enterParents = parents
.enter()
.append('g')
.attr('class', 'parent')
.attr('width', 800)
.attr('height', 360)
// 将合并的 g.parent 组存储在变量中
parentsMerged = enterParents.merge(parents) // 新
// 存储更新的元素选择
parentElements = parents.select('element.parent') // 新
enterParents
.append('element')
.attr('class', 'parent')
// 新 - 使用 merge() 更新合并
.merge(parentElements)
.attr('fill', d => d.light)
// 进入嵌套的 g.parent 内部的子组 g.child
const children = parents.select('g.child')
enterChild = enterParents
.append('g')
.attr('class', 'child')
// 将合并的 g.child 组存储在变量中
childrenMerged = enterChildren.merge(children) // 新
// 存储更新的元素选择
childElements = children.select('element.child') // 新
enterChild
.append('element')
.attr('class', 'child')
// 新 - 使用 merge() 更新合并
.merge(childElements)
.attr('fill', d => d.light)
// 为悬停在任何 g.parent 上的鼠标位置添加事件监听器
parentsMerged // 新
.on('mousemove', function(e) {
const mouse = d3.pointer(e)
const mouseX = mouse[0]
const mouseY = mouse[1]
childrenMerged // 新
.attr('transform', `translate( ${mouseX}, ${mouseY})`)
})
}
英文:
I figured it out, the process just involved storing the merged enter + update selections into one variable to invoke further down the code. The code in my question only involved tracking the mouse position on the updated selection, hence the mouse position only behaving as expected on update and not enter.
Below is the modified code, where the update is now performed on the merge portion of the code. If I wanted to I could separately handle updating from entering, by calling the merged variable on another line.
// dummy data
let data = [
{ key: 1,
light = #ffffff,
dark = #000000 },
...
, // note: n represents the nth value
{ key: n,
light = #eeeeee,
dark = #222222 }
]
//=== D3 ====
const svg = d3.select('svg')
const parents = svg
.selectAll('g.parent')
.data(data, d => d.key) // maintaining object constancy using identifier
// ENTER
const enterParents = parents
.enter()
.append('g')
.attr('class', 'parent')
.attr('width', 800)
.attr('height', 360)
// Storing merged g.parent groups in variable
parentsMerged = enterParents.merge(parents) // NEW
// storing elements selection in var for update
parentElements = parents.select('element.parent') // NEW
enterParents
.append('element')
.attr('class', 'parent')
// NEW - update combined using merge()
.merge(parentElements)
.attr('fill', d => d.light)
// entering nested child group g.child per g.parent
const children = parents.select('g.child')
enterChild = enterParents
.append('g')
.attr('class', 'child')
// Storing merged g.child groups in variable
childrenMerged = enterChildren.merge(children) // NEW
// storing elements selection in var for update
childElements = children.select('element.child') // NEW
enterChild
.append('element')
.attr('class', 'child')
// NEW - update combined using merge()
.merge(childElements)
.attr('fill', d => d.light)
// adding event listener for mouse position hovering over any g.parent
parentsMerged // NEW
.on('mousemove', function(e) {
const mouse = d3.pointer(e)
const mouseX = mouse[0]
const mouseY = mouse[1]
childrenMerged // NEW
.attr('transform', `translate( ${mouseX}, ${mouseY})`)
})
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论