これは、同様の視覚化要件に対するソリューションですが、機能要件はわずかに異なります。
ここでは、あなたの場合のように、ノードは異なる「ゾーン」にグループ化されています。その後、ユーザーはあるゾーンから別のゾーンにノードをドラッグする必要がありますが、ノードとゾーンの特性に基づいて、一部のゾーンは無効または「無効」になっています。
標準的な力を使用します
that.simulation = this.d3.forceSimulation()
.force('collide',d3.forceCollide()
.radius(d => {
return d.type === 'count' ? 60 : 30
}))
// .force('charge',d3.forceManyBody()
// .strength(10))
.on('tick',ticked)
以下のコードは、dragged
ノードをデッド ゾーンから制限するハンドラです。
これは完全にはデバッグされておらず、10 月以降は触れられていませんが、役立つことを願っています。
ドラッグされたハンドラーの下には、ticked
最初とドラッグ後に要素を配置する関数があります。
引きずられた
重要な部分は、これらのコメントの後の約半分です。
// positioning of dragged node under cursor
// respecting all deadzone and perimiter boundaries and node radius
// drag in progress handler
// d = the d3 object associated to the dragged circle
function dragged(d) {
that.trace()
let debugcoord = [10,20]
let r = RAD_KEY + 4
let dx = d3.event.dx
let dy = d3.event.dy
let fx = d.fx
let fy = d.fy
let nx = fx+dx
let ny = fy+dy
// DEBUGGING CODE BELOW, DO NOT DELETE
// let curX = Math.round(d3.event.sourceEvent.clientX-that.g.node().getClientRects()[0].left)
// let curY = Math.round(d3.event.sourceEvent.clientY-that.g.node().getClientRects()[0].top)
// that.g.selectAll('text.coordinates').remove()
// that.g
// .append('text')
// .classed('coordinates',true)
// .attr('x',debugcoord[0])
// .attr('y',debugcoord[1])
// .text(`${curX},${curY}`)
// check out this fx,fy description for reference:
// https://stackoverflow.com/a/51548821/4256677
// deadzones are the inverse of livezones
let deadzone = []
// the names of all valid transitions, used to calculate livezones
let trans = !!d.trans ? d.trans.map(t => t.to.name) : []
// the name value of the workflow status of the dragged node
let name = d.name
// all the zones:
let zones = d3.selectAll('g.zone.group > rect.zone')
// the maximum x+width value of all nodes in 'zones' array
let right = d3.max(zones.data(), n => parseFloat(n.x)+parseFloat(n.width))
// the maximum y+height value of all nodes in 'zones' array
let bottom = d3.max(zones.data(), n => parseFloat(n.y)+parseFloat(n.height))
// the zones which represent valid future states to transition
// the dragged node (issue)
let livezones = zones.filter(function(z,i,nodes) {
// the current zone object
let zone = d3.select(this)
// the name of the current zone object
let zonename = zone.attr('data-zone')
// boolean referring to the current zone representing a valid future
// state for the node
let isLive = trans.includes(zonename) || name == zonename
// deadzone recognition and caching
if(!isLive)
{
let coords = {name:zonename,
x1:parseFloat(z.x),
x2:parseFloat(z.x)+parseFloat(z.width),
y1:parseFloat(z.y),
y2:parseFloat(z.y)+parseFloat(z.height)}
deadzone.push(coords)
}
return isLive
}).classed('live',true) // css for livezones
d3.selectAll('rect.zone:not(.live)').classed('dead',true) // css for deadzones
// positioning of dragged node under cursor
// respecting all deadzone and perimiter boundaries and node radius
that.nodes.filter(function(d) { return d.dragging; })
.each(function(d) {
if(deadzone.length > 0)
{
d.fx += deadzone.reduce((a,c) => {
a =
// node is in graph
(nx > 0 + r && nx < right - r)
// deadzone is in left column and node is to the right or above or below
&& ((c.x1 == 0 && (nx > c.x2 + r || ny < c.y1 - r || ny > c.y2 + r))
// or deadzone is in the right column and node is to the left, above or below
|| (c.x2 == right && (nx < c.x1 - r || ny < c.y1 - r || ny > c.y2 + r))
// or deadzone is not in left column and node is to the left, right, above or below
|| (c.x1 > 0 && (nx < c.x1 - r || nx > c.x2 + r || ny < c.y1 - r || ny > c.y2 + r))
)
? dx : 0
return a
},0)
d.fy += deadzone.reduce((a,c) => {
a =
// node is in graph
(ny > 0 + r && ny < bottom - r)
// deadzone is in top row and node is below or to the left or right
&& ((c.y1 == 0 && (ny > c.y2 + r || nx < c.x1 - r || nx > c.x2 + r))
// or deadzone is in the right column and node is to the left, above or below
|| (c.y2 == bottom && (ny < c.y1 - r || nx < c.x1 - r || nx > c.x2 + r))
// or deadzone is not in top row and node is above, below, left or right
|| (c.y1 > 0 && (ny < c.y1 - r || ny > c.y2 + r || nx < c.x1 - r || nx > c.x2 + r))
)
? dy : 0
// DEBUGGING CODE BELOW, DO NOT DELETE
// that.g
// .append('text')
// .classed('coordinates',true)
// .attr('x',debugcoord[0])
// .attr('y',debugcoord[1]+25)
// .text(`${Math.round(nx)},${Math.round(ny)} vs ${r},${r},${right-r},${bottom-r}`)
// that.g
// .append('text')
// .classed('coordinates',true)
// .attr('x',10)
// .attr('x',debugcoord[0])
// .attr('y',debugcoord[1]+50)
// .text(`dz coords: ${c.x1},${c.y1} ${c.x2},${c.y2}`)
// that.g
// .append('text')
// .classed('coordinates',true)
// .attr('x',debugcoord[0])
// .attr('y',debugcoord[1]+75)
// .text(c.name)
return a
},0)
}
else
{
d.fx += dx
d.fy += dy
}
})
}
刻んだ
重要な部分はコメントの後にあります// if were no longer dragging
function ticked(e) {
if(!!that.links && that.links.length > 0)
{
that.links
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; })
}
that.nodes
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.each(function(d) {
if(typeof d.selected === 'undefined')
d.selected = false
if(typeof d.previouslySelected === 'undefined')
d.previouslySelected = false
})
that.labels
.attr("x", function(d) { return d.x })
.attr("y", function(d) {
return d.type === 'count' ? d.y+6 : d.y+4
})
.attr('class',(d) => { return that.getClassFromNodeName(d.name)})
.classed('count', (d) => {
return d.type === 'count' ? true : false
})
// if were no longer dragging
if(!that.dragging)
{
let k = 4*this.alpha()
that.nodes.each(function(n,i) {
let zclass = that.getClassFromNodeName(n.name)
let z = that.zones[zclass]
n.x += (z.x + z.width/2 - n.x) * k
n.y += (z.y + z.height/2 - n.y) * k
})
}
that.nodes
// .each(pos)
.attr('cx',d => { return d.x }) //boundary(d,'x')})
.attr('cy',d => { return d.y }) //boundary(d,'y')})
that.labels
// .each(pos)
.attr('x',d => { return d.x }) //boundary(d,'x')})
.attr('y',d => { return d.y + (d.type === 'count'?6:4) }) //boundary(d,'y') + (d.type === 'count'?6:4)})
}