Самая важная вещь в использовании имитации силы, чтобы избежать наложения точек на точечной диаграмме, - это использование методов 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>