I want to apply a scale to circles generated with d3 force from an array.
This code produces the right x axis, but there are loads of circles produced with the class 'goalamount' and they are all offscreen by tens of thousands of pixels. There should only be six circles in the goalamount class and they should all scale to the xaxis - what am I doing wrong?
const data = [{
x: 2020,
cx: 0,
colour: "#69306D",
scY: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x: 2020,
cx: 0,
colour: "#247BA0",
scY: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x: 2020,
cx: 0,
colour: "#3F762C",
y1: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x: 2020,
cx: 0,
colour: "#F25F5C",
y1: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x: 2022,
cx: 0,
colour: "#0C3957",
y1: 0,
y2: 170,
rad: 10,
amt: 5000
},
{
x: 2055,
cx: 0,
colour: "#BF802F",
y1: 0,
y2: 50,
rad: 10,
amt: 15000
}
];
const maxYear = Math.max.apply(Math, data.map(function(o) {
return o.x;
}));
const svg = d3.select("svg");
const pxX = svg.attr("width");
const pxY = svg.attr("height");
let tickLabelOffset = 170;
let minDotX = Math.min.apply(Math, data.map(function(o) {
return o.y1;
}))
if (minDotX < -20) {
tickLabelOffset += minDotX + 20;
}
const makeScale = (arr, accessor, range) => {
return d3.scaleLinear()
.domain(d3.extent(arr, accessor))
.range(range)
.nice()
}
const thisYear = new Date().getFullYear()
let tickTens = [];
for (let i = thisYear; i < maxYear; i++) {
if (i % 10 === 0) {
tickTens.push(i)
}
}
const scX = makeScale(data, d => d.x, [0, pxX - 200]);
const scX1 = makeScale(data, d => d.x, [0, pxX - 2020]);
const scY = d3.scaleLinear().domain([0, 100]).range([0, 100]);
const g = d3.axisBottom(scX).tickValues(
tickTens.map((tickVal) => {
return tickVal
})
)
const rad = d3.scaleLinear()
.domain(d3.extent(data, d => d.rad))
.range([3, 10]);
const amt = d3.scaleLinear()
.domain(d3.extent(data, d => d.amt))
.range([20, 50]);
for (let dotindex = 0; dotindex < data.length; dotindex++) {
if (data[dotindex - 1]) {
if (data[dotindex - 1].x === data[dotindex].x) {
data[dotindex].scY = data[dotindex - 1].scY - 20
}
}
}
const ticked = () => {
var u = d3.select('svg')
.append("g")
.attr("class", "goalAmounts")
.selectAll('goalAmounts')
.data(data)
u.enter()
.append("circle")
// .attr( "transform", "translate(" + 2000 + "," + 50 + ")")
.attr("r", d => amt(d.amt))
.merge(u)
.attr("fill", d => d.colour)
.attr("cx", d => scX(d.x))
.attr("cy", d => scY(d.y2))
u.exit().remove()
}
svg.append("g")
.attr("transform", "translate(" + 50 + "," + (pxY - 200) + ")")
.call(g)
.selectAll(".tick text")
.attr("fill", "#7A7A7A")
svg.selectAll("circle")
.data(data)
.enter()
.append("g")
.attr("class", "circles")
.append("circle")
.attr("transform", "translate(" + 100 + "," + 650 + ")")
.attr("fill", "white")
.attr("stroke", d => d.colour)
.attr("stroke-width", "2px")
.attr("cx", d => scX(d.x))
.attr("cy", d => scY(d.y2))
.attr("r", d => rad(d.rad));
svg.selectAll(".domain")
.attr("stroke", "#BDBDBD")
.attr("stroke-width", "2px")
.attr("transform", "translate(" + 50 + "," + 150 + ")")
svg.selectAll(".tick line")
.attr("stroke", "#BDBDBD")
.attr("stroke-width", "4px")
.attr("transform", "translate(" + 50 + "," + 150 + ")")
svg.selectAll(".tick text")
.attr("font-size", 20)
.attr("transform", "translate(" + 50 + "," + tickLabelOffset + ")")
.attr("font-weight", "bold")
.attr("dy", "0.5em")
d3.forceSimulation(data)
.force('charge', d3.forceManyBody())
.force('center', d3.forceCenter(pxX / 2, pxY / 2))
.force('collision', d3.forceCollide().radius(function(d) {
return d.amt
}))
.on('tick', ticked);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="App">
<svg id="demo1" width="1200" height="700">
</svg>
</div>
Use d3.forceX
and d3.forceY
instead, so you draw the nodes towards their intended position. Also, d3-force
populates x
and y
properties of the nodes, so you need to use d.x1
or something instead. scX(d.x)
caused the huge values of the nodes.
const data = [{
x1: 2020,
cx: 0,
colour: "#69306D",
scY: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x1: 2020,
cx: 0,
colour: "#247BA0",
scY: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x1: 2020,
cx: 0,
colour: "#3F762C",
y1: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x1: 2020,
cx: 0,
colour: "#F25F5C",
y1: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x1: 2022,
cx: 0,
colour: "#0C3957",
y1: 0,
y2: 170,
rad: 10,
amt: 5000
},
{
x1: 2055,
cx: 0,
colour: "#BF802F",
y1: 0,
y2: 50,
rad: 10,
amt: 15000
}
];
const maxYear = Math.max.apply(Math, data.map(function(o) {
return o.x1;
}));
const svg = d3.select("svg");
const pxX = svg.attr("width");
const pxY = svg.attr("height");
let tickLabelOffset = 170;
let minDotX = Math.min.apply(Math, data.map(function(o) {
return o.y1;
}))
if (minDotX < -20) {
tickLabelOffset += minDotX + 20;
}
const makeScale = (arr, accessor, range) => {
return d3.scaleLinear()
.domain(d3.extent(arr, accessor))
.range(range)
.nice()
}
const thisYear = new Date().getFullYear()
let tickTens = [];
for (let i = thisYear; i < maxYear; i++) {
if (i % 10 === 0) {
tickTens.push(i)
}
}
const scX = makeScale(data, d => d.x1, [0, pxX - 200]);
const scX1 = makeScale(data, d => d.x1, [0, pxX - 2020]);
const scY = d3.scaleLinear().domain([0, 100]).range([0, 100]);
const g = d3.axisBottom(scX).tickValues(
tickTens.map((tickVal) => {
return tickVal
})
)
const rad = d3.scaleLinear()
.domain(d3.extent(data, d => d.rad))
.range([3, 10]);
const amt = d3.scaleLinear()
.domain(d3.extent(data, d => d.amt))
.range([20, 50]);
for (let dotindex = 0; dotindex < data.length; dotindex++) {
if (data[dotindex - 1]) {
if (data[dotindex - 1].x1 === data[dotindex].x1) {
data[dotindex].scY = data[dotindex - 1].scY - 20
}
}
}
const ticked = () => {
circles
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}
svg.append("g")
.attr("transform", "translate(" + 50 + "," + (pxY - 200) + ")")
.call(g)
.selectAll(".tick text")
.attr("fill", "#7A7A7A")
const circles = svg.append("g")
.attr("class", "circles")
.attr( "transform", "translate(" + 100 + "," + 100 + ")")
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("fill", "white")
.attr("stroke", d => d.colour)
.attr("stroke-width", "2px")
.attr("cx", d => scX(d.x1))
.attr("cy", d => scY(d.y2))
.attr("r", d => rad(d.rad));
svg.selectAll(".domain")
.attr("stroke", "#BDBDBD")
.attr("stroke-width", "2px")
.attr("transform", "translate(" + 50 + "," + 150 + ")")
svg.selectAll(".tick line")
.attr("stroke", "#BDBDBD")
.attr("stroke-width", "4px")
.attr("transform", "translate(" + 50 + "," + 150 + ")")
svg.selectAll(".tick text")
.attr("font-size", 20)
.attr("transform", "translate(" + 50 + "," + tickLabelOffset + ")")
.attr("font-weight", "bold")
.attr("dy", "0.5em")
d3.forceSimulation(data)
.force("x", d3.forceX(d => scX(d.x1)))
.force("y", d3.forceY(d => scY(d.y2)))
.force('collision', d3.forceCollide().radius(d => rad(d.rad)))
.on("tick", ticked);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="App">
<svg id="demo1" width="1200" height="700">
</svg>
</div>