javascript - 矩形容器内的边界圆节点
问题描述
前几天我一直被这个问题困扰,但不幸的是无法找到解决方案。我正在尝试实现此处显示的行为,但通常根据每个节点的属性对各种容器执行此操作。我想联系并询问是否有任何已知的方法可以做到这一点。
下面是我的 JSFiddle 是我目前拥有的一个示例 - 分配给随机组号的多个节点和一个 barView 函数,该函数根据它们的组分隔这些节点。我希望将这些节点限制在它们各自条形的尺寸内,以便拖动这些节点无法将它们从盒子中移除,但它们可以在其中移动(相互反弹)。我非常感谢您在这方面的帮助。
为简单起见,我在每个节点中制作了与“总计”字段相关的条(以显示 SVG 尺寸中的条),但这些将与我的实现中的大小相关,类似于卷。
我已经能够使用以下代码组织节点的 x 位置,其中位置基于组:
simulation.force('x', d3.forceX().strength(1).x(function(d) {
return xscale(d.group); // xvariable
}));
使用此代码,我不确定如何在矩形的尺寸内工作,或保持圆圈可以在其中反弹的边界。我会很感激你在这方面的帮助!
太感谢了!
我的小提琴:http: //jsfiddle.net/abf2er7z/2/
解决方案
一种可能的解决方案是设置一个新tick
函数,该函数使用Math.max
并Math.min
获取这些矩形的边界:
simulation.on("tick", function() {
node.attr("cx", function(d) {
return d.x = Math.min(Math.max(xscale(d.group) - 20 + d.radius, d.x), xscale(d.group) + 20 - d.radius);
})
.attr("cy", function(d) {
return d.y = Math.min(Math.max(0.9 * height - heightMap[d.group] + d.radius, d.y), height - d.radius);
});
});
这是演示:
var width = 900,
height = 400;
var groupList = ['Group A', 'Group B', 'Group C', 'Group D'];
var data = d3.range(200).map(d => ({
id: d,
group: groupList[getRandomIntegerInRange(0, 3)],
size: getRandomIntegerInRange(1, 100),
total: getRandomIntegerInRange(1, 10)
}))
var svg = d3.select("body")
.append("svg")
.attr("viewBox", "0 0 " + (width) + " " + (height))
.attr("preserveAspectRatio", "xMidYMid meet")
.attr('width', "100%")
.attr('height', height)
.attr('id', 'svg')
.append('g')
.attr('id', 'container')
.attr('transform', 'translate(' + 0 + ', ' + 0 + ')');
simulation = d3.forceSimulation();
data.forEach(function(d, i) {
d.radius = Math.sqrt(d['size']);
});
colorScale = d3.scaleOrdinal(d3.schemeCategory10);
node = svg.append("g")
.attr("class", "node")
.selectAll(".bubble")
.data(data, function(d) {
return d.id;
})
.enter().append("circle")
.attr('class', 'bubble')
.attr('r', function(d) {
return d.radius;
}) // INITIALIZED RADII TO 0 HERE
.attr("fill", function(d) {
// initially sets node colors
return colorScale(d.group);
})
.attr('stroke-width', 0.5)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
function dragstarted(d) {
if (!d3.event.active) {
simulation.alpha(.07).restart()
}
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alpha(0.07).restart()
d.fx = null;
d.fy = null;
// Update and restart the simulation.
simulation.nodes(data);
}
simulation
.nodes(data)
.force("x", d3.forceX().strength(0.1).x(width / 2))
.force("y", d3.forceY().strength(0.1).y(height / 2))
.force("collide", d3.forceCollide().strength(0.7).radius(function(d) {
return d.radius + 0.5;
}).iterations(2))
.on("tick", function() {
node
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
});
function barView() {
var buff = width * 0.12
var leftBuff = buff;
var rightBuff = width - buff;
var scale;
xscale = d3.scalePoint()
.padding(0.1)
.domain(groupList)
.range([leftBuff, rightBuff]);
// Save double computation below.
heightMap = {}
groupList.forEach(function(d) {
currVarTotal = data.filter(function(n) {
return n.group === d;
}).reduce(function(a, b) {
return a + +b.total;
}, 0);
heightMap[d] = currVarTotal;
})
var rects = svg.selectAll('.rect')
.data(groupList)
.enter()
.append('rect')
.attr('x', function(d) {
return xscale(d) - 20
})
.attr('y', function(d) {
return 0.9 * height - heightMap[d];
})
.attr('width', 40)
.attr('height', function(d) {
return heightMap[d];
})
.attr('fill', 'transparent')
.attr('stroke', function(d) {
return colorScale(d)
})
.attr('stroke-width', 2)
.attr('class', 'chartbars');
drawTheAxis(xscale);
simulation.force('x', d3.forceX().strength(1).x(function(d) {
return xscale(d.group); // xvariable
})).on("tick", function() {
node
.attr("cx", function(d) {
return d.x = Math.min(Math.max(xscale(d.group) - 20 + d.radius, d.x), xscale(d.group) + 20 - d.radius);
})
.attr("cy", function(d) {
return d.y = Math.min(Math.max(0.9 * height - heightMap[d.group] + d.radius, d.y), height - d.radius);
});
});
currHeights = {}
Object.keys(heightMap).forEach(d => {
currHeights[d] = 0.9 * height
});
// restart the simulation
simulation.alpha(0.07).restart();
function drawTheAxis(scale) {
var bottomBuffer = 0.9 * height;
// create axis objects
var xAxis = d3.axisBottom(xscale);
// Draw Axis
var gX = svg.append("g") // old: nodeG.append
.attr("class", "xaxis")
.attr('stroke-width', 2)
.attr("transform", "translate(0," + height + ")")
.attr('opacity', 0)
.call(xAxis)
.transition()
.duration(250)
.attr('opacity', 1)
.attr("transform", "translate(0," + bottomBuffer + ")");
}
}
function getRandomIntegerInRange(min, max) {
return Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min) + 1)) + Math.ceil(min);
}
setTimeout(function() {
barView();
}, 1500);
<script src="https://d3js.org/d3.v5.min.js"></script>
请记住,这不是最终解决方案,而只是一般指导:转换、数学(使用那些神奇的数字)和音阶需要改进。
推荐阅读
- java - 使用矢量时在 Java 中出现运算符错误
- python - 如何 AutoDoc 文档从函数 DocString 中排除 SQLAlchemy 库 DocString?
- ios - 如何在购买通知中为苹果设置测试和生产环境
- javascript - Vue vuetify 数据表动态单元格加载
- python-3.x - Pyspark 列:将字符串转换为日期类型
- go - 使用带有 GoDog 测试框架的断言库
- javascript - 如何计算随机图片的点击次数?
- python - Selenium ConnectionRefusedError: [WinError 10061] 由于目标机器主动拒绝,无法建立连接
- javascript - 如何在新 vue 之后设置 vue-data 并进行数据绑定
- c++ - 我的二叉搜索树的实现不起作用,因为我不断收到我的节点和 typedef 项目未定义的错误