我正在尝试在D3中使用两个X和Y轴的力模拟绘制散点图,我想应用力布局,只是为了避免重叠的点,但是我得到了完全相反的效果(点是重叠的,并且位置不正确)
到目前为止,这是我的代码:
// Create SVG and margins
var margin = {top: 52, right: 78, bottom: 52, left: 78}
var myWidth = 900 - margin.left - margin.right
var myHeight = 450 - margin.top - margin.bottom
var svg = d3.select('body').append('svg')
.attr('width', myWidth + margin.left + margin.right)
.attr('height', myHeight + margin.top + margin.bottom)
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + ", " + margin.top + ")")
// Scale
var y = d3.scaleLinear()
.domain([2,10])
.range([myHeight, 0])
var x = d3.scaleLinear()
.domain([0,100])
.range([0, myWidth])
// Axis
var yAxisCall = d3.axisLeft(y).tickSize(10)
g.append("g")
.attr("class", "y-axis")
.call(yAxisCall)
var xAxisCall = d3.axisBottom(x).tickSize(10)
g.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0, " + myHeight + ")")
.call(xAxisCall)
// Helper Functions
var myIbus = function(d,i){
if (d.ibus) {
return d.ibus[1] ? (y((d.ibus[0] + d.ibus[1])/2)) : (y(d.ibus[0]))
}
else return 0
}
var myABV = function(d,i){
if (d.abv) {
return d.abv[1] ? (x((d.abv[0] + d.abv[1])/2)) : (x(d.abv[0]))
}
else return 0
}
// Force Simulation
var simulation = d3.forceSimulation(nodes)
.force('collide', d3.forceCollide())
.on('tick', ticked)
function ticked() {
var myCircles = g.selectAll('circle')
.data(nodes)
myCircles.enter()
.append('circle')
.attr("cx", myIbus)
.attr("cy", myABV)
.attr("r", 8)
myCircles.exit().remove()
}
我在https://bl.ocks.org/Jesus82/ad5c6fb46f8be5a9d3e763f8a1ba03d7中有一个工作示例,其中包含我正在使用的数据(我想根据其ABV-酒精含量和IBUS苦味来形象化啤酒样式),并且数据在范围内,只是用他们的意思。
谢谢!
答案 0 :(得分:1)
关于使用力模拟来避免散点图中重叠的点,最重要的是使用d3.forceX
和d3.forceY
方法来设置位置,而d3.forceCollide
只是为了避免重叠。
因此,您的模拟应为:
var simulation = d3.forceSimulation(nodes)
.force('collide', d3.forceCollide().radius(8))
.force('x', d3.forceX(myIbus))
.force('y', d3.forceY(myABV))
.on('tick', ticked);
在您的ticked
函数中:
function ticked() {
myCircles.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
});
};
您可以使用其中的strengths
种力量:给forceX/Y
力量提供更多的力量,可以使散点更加准确,但要重叠的点更多;为forceCollide
增强强度会减少重叠,但会使可视化效果不准确。
除此之外,您还有一些小问题:
ticked
功能; myIbus
和myABV
方法似乎有错误的尺度(只需交换它们)。这是您更新的代码:
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body {
margin: 0;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
</style>
</head>
<body>
<script>
var nodes = [{
name: 'abbey_dubbel',
abv: [6, 7.6],
ibus: [15, 25]
},
{
name: 'abbey_tripel',
abv: [7.5, 9.5],
ibus: [20, 40]
},
{
name: 'ale',
abv: [0],
ibus: [0]
},
{
name: 'amber_ale',
abv: [0],
ibus: [0]
},
{
name: 'amber_lager',
abv: [4.7, 5.5],
ibus: [18, 30]
},
{
name: 'american_IPA',
abv: [6, 14],
ibus: [40, 70]
},
{
name: 'american_pale_ale',
abv: [4.5, 6.2],
ibus: [30, 50]
},
{
name: 'american_strong_ale',
abv: [8, 12],
ibus: [30, 60]
},
{
name: 'baltic_porter',
abv: [6.5, 9.5],
ibus: [20, 40]
},
{
name: 'barley_wine',
abv: [8, 12],
ibus: [50, 100]
},
{
name: 'belgian_ale',
abv: [8, 5.5],
ibus: [20, 30]
},
{
name: 'belgian_strong_ale',
abv: [7.5, 10.5],
ibus: [22, 35]
},
{
name: 'berliner_weisse',
abv: [2.8, 3.8],
ibus: [3, 8]
},
{
name: 'biere_de_garde',
abv: [6, 8.5],
ibus: [18, 28]
},
{
name: 'black_IPA',
abv: [5.5, 9],
ibus: [50, 90]
},
{
name: 'blond_ale',
abv: [6, 7.5],
ibus: [15, 30]
},
{
name: 'brown_ale',
abv: [4.2, 5.4],
ibus: [20, 30]
},
{
name: 'brut_ipa',
abv: [5, 7.5],
ibus: [40, 60]
},
{
name: 'cider',
abv: [0],
ibus: [0]
},
{
name: 'doppelbock',
abv: [7, 10],
ibus: [16, 26]
},
{
name: 'dunkel',
abv: [4.5, 5.6],
ibus: [18, 28]
},
{
name: 'ESB',
abv: [4.6, 6.2],
ibus: [30, 50]
},
{
name: 'foreign_extra_stout',
abv: [6.3, 8],
ibus: [50, 70]
},
{
name: 'fruit_beer',
abv: [2, 8],
ibus: [40]
},
{
name: 'fruity_lambic',
abv: [5, 7],
ibus: [10]
},
{
name: 'gose',
abv: [4.2, 4.8],
ibus: [5, 12]
},
{
name: 'gueuze_lambic',
abv: [5, 8],
ibus: [10]
},
{
name: 'imperial_IPA',
abv: [7.5, 10],
ibus: [60, 120]
},
{
name: 'imperial_pils',
abv: [0],
ibus: [0]
},
{
name: 'imperial_porter',
abv: [4.8, 6.5],
ibus: [25, 50]
},
{
name: 'imperial_stout',
abv: [5, 7.5],
ibus: [40, 60]
},
{
name: 'india_style_lager',
abv: [0],
ibus: [0]
},
{
name: 'IPA',
abv: [5, 7.5],
ibus: [40, 60]
},
{
name: 'lager',
abv: [0],
ibus: [0]
},
{
name: 'lambic',
abv: [5, 6.5],
ibus: [10]
},
{
name: 'landbier',
abv: [4.7, 7.4],
ibus: [16, 22]
},
{
name: 'neipa',
abv: [6, 9],
ibus: [25, 60]
},
{
name: 'old_ale',
abv: [5.5, 9],
ibus: [30, 60]
},
{
name: 'pale_lager',
abv: [4.6, 6],
ibus: [18, 25]
},
{
name: 'pilsener',
abv: [4.4, 5.2],
ibus: [22, 40]
},
{
name: 'porter',
abv: [4, 5.4],
ibus: [28, 35]
},
{
name: 'premium_lager',
abv: [4.2, 5.8],
ibus: [30, 45]
},
{
name: 'quadrupel',
abv: [8, 12],
ibus: [20, 35]
},
{
name: 'saison',
abv: [3.5, 9.5],
ibus: [20, 35]
},
{
name: 'scotch_ale',
abv: [6.5, 10],
ibus: [17, 35]
},
{
name: 'session_IPA',
abv: [3, 5],
ibus: [35, 60]
},
{
name: 'smoked',
abv: [0],
ibus: [0]
},
{
name: 'sour_red_brown',
abv: [4.6, 6.5],
ibus: [10, 25]
},
{
name: 'sour_wild_ale',
abv: [0],
ibus: [0]
},
{
name: 'specialty_grain',
abv: [0],
ibus: [0]
},
{
name: 'stout',
abv: [4, 6],
ibus: [20, 40]
},
{
name: 'sweet_stout',
abv: [4, 6],
ibus: [20, 40]
},
{
name: 'weissbier',
abv: [4.3, 5.6],
ibus: [8, 15]
},
{
name: 'weizen_bock',
abv: [6.5, 9],
ibus: [15, 30]
},
{
name: 'wheat_ale',
abv: [4, 5.5],
ibus: [15, 30]
},
{
name: 'witbier',
abv: [4.5, 5.5],
ibus: [8, 20]
}
]
// Create SVG and margins
var margin = {
top: 52,
right: 78,
bottom: 52,
left: 78
}
var myWidth = 900 - margin.left - margin.right
var myHeight = 450 - margin.top - margin.bottom
var svg = d3.select('body').append('svg')
.attr('width', myWidth + margin.left + margin.right)
.attr('height', myHeight + margin.top + margin.bottom)
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + ", " + margin.top + ")")
// Scale
var y = d3.scaleLinear()
.domain([2, 10])
.range([myHeight, 0])
var x = d3.scaleLinear()
.domain([0, 100])
.range([0, myWidth])
// Axis
var yAxisCall = d3.axisLeft(y).tickSize(10)
g.append("g")
.attr("class", "y-axis")
.call(yAxisCall)
var xAxisCall = d3.axisBottom(x).tickSize(10)
g.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0, " + myHeight + ")")
.call(xAxisCall)
// Helper Functions
var myIbus = function(d, i) {
if (d.ibus) {
return d.ibus[1] ? (x((d.ibus[0] + d.ibus[1]) / 2)) : (x(d.ibus[0]))
} else return 0
}
var myABV = function(d, i) {
if (d.abv) {
return d.abv[1] ? (y((d.abv[0] + d.abv[1]) / 2)) : (y(d.abv[0]))
} else return 0
}
// Force Simulation
var simulation = d3.forceSimulation(nodes)
.force('collide', d3.forceCollide().radius(8))
.force('x', d3.forceX(myIbus))
.force('y', d3.forceY(myABV))
.on('tick', ticked);
var myCircles = g.selectAll('circle')
.data(nodes)
myCircles = myCircles.enter()
.append('circle')
.attr("r", 8)
.merge(myCircles);
myCircles.exit().remove()
function ticked() {
myCircles.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
});
};
</script>
</body>