d3 force 没有正确应用 x 刻度

d3 force not applying x scale properly

我想对使用数组的 d3 力生成的圆应用比例尺。

这段代码生成了正确的 x 轴,但是 class 'goalamount' 生成了很多圆,它们都在屏幕外数万像素。目标数量中应该只有六个圆圈 class 并且它们都应该缩放到 x 轴 - 我做错了什么?

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>

改用 d3.forceXd3.forceY,这样您就可以将节点绘制到它们的预期位置。此外,d3-force 填充节点的 xy 属性,因此您需要使用 d.x1 或其他东西。 scX(d.x)造成节点的巨大价值。

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>