问题描述
我想对用d3力从数组中生成的圆应用比例尺。
此代码产生了正确的x轴,但是使用“ goalamount”类产生了许多圆,它们都在屏幕外成千上万个像素。 Goalamount类中应该只有六个圆圈,并且它们都应该缩放到xaxis-我在做什么错了?
const data = [{
x: 2020,cx: 0,colour: "#69306D",scY: 0,y2: 50,rad: 10,amt: 5000
},{
x: 2020,colour: "#247BA0",colour: "#3F762C",y1: 0,colour: "#F25F5C",{
x: 2022,colour: "#0C3957",y2: 170,{
x: 2055,colour: "#BF802F",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,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"," + 150 + ")")
svg.selectAll(".tick line")
.attr("stroke","4px")
.attr("transform"," + 150 + ")")
svg.selectAll(".tick text")
.attr("font-size",20)
.attr("transform"," + 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>
解决方法
请改用d3.forceX
和d3.forceY
,以便将节点拉向其预期位置。另外,d3-force
填充节点的x
和y
属性,因此您需要使用d.x1
或其他方法。 scX(d.x)
导致了节点的巨大价值。
const data = [{
x1: 2020,cx: 0,colour: "#69306D",scY: 0,y2: 50,rad: 10,amt: 5000
},{
x1: 2020,colour: "#247BA0",colour: "#3F762C",y1: 0,colour: "#F25F5C",{
x1: 2022,colour: "#0C3957",y2: 170,{
x1: 2055,colour: "#BF802F",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,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"," + 150 + ")")
svg.selectAll(".tick line")
.attr("stroke","4px")
.attr("transform"," + 150 + ")")
svg.selectAll(".tick text")
.attr("font-size",20)
.attr("transform"," + 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>