Javascript d3 forceX未将圆定位在其x轴值附近

Javascript d3 forceX未将圆定位在其x轴值附近,javascript,reactjs,d3.js,Javascript,Reactjs,D3.js,我的d3图看起来很棒-除了一件事之外,它很漂亮-我的圆的x位置似乎与数组中给定的x值不太一致 我有一个对象数组,如下所示: { x: 2020, cx: 0, colour: "#F25F5C", y1: 0, y2: 200, rad: 10, amt: 5000 }, 我有两组圆。第一个非常靠近X轴,表示确切的日期,它们的颜色与较大圆圈的颜色相匹配,其半径在较大的范围内变化

我的d3图看起来很棒-除了一件事之外,它很漂亮-我的圆的x位置似乎与数组中给定的x值不太一致

我有一个对象数组,如下所示:

  {
      x: 2020,
      cx: 0,
      colour: "#F25F5C",
      y1: 0,
      y2: 200,
      rad: 10,
      amt: 5000
    }, 
我有两组圆。第一个非常靠近X轴,表示确切的日期,它们的颜色与较大圆圈的颜色相匹配,其半径在较大的范围内变化,我对这些圆圈施加d3力,以更有效地定位它们

问题在于,较大的圆的中心位置应该接近(x,y2)给出的数组中的值。但事实并非如此,而且我的镊子似乎不起作用

这是我的完整代码:

import React, {useState, useEffect} from 'react';
import * as d3 from 'd3';
import './App.css';

function App() {
  const [maxYear, setMaxYear] = useState();
  const [data, setData] = useState(
   [{
      x: 2020,
      colour: "#69306D",
      y1: 0,
      y2: 50,
      rad: 10,
      amt: 2000
    },
    {
      x: 2020,
      colour: "#247BA0",
      y1: 0,
      y2: 100,
      rad: 10,
      amt: 5000
    },
    {
      x: 2020,
      colour: "#3F762C",
      y1: 0,
      y2: 150,
      rad: 10,
      amt: 7500
    },
    {
      x: 2020,
      colour: "#F25F5C",
      y1: 0,
      y2: 200,
      rad: 10,
      amt: 5000
    },
    {
      x: 2022,
      colour: "#0C3957",
      y1: 0,
      y2: 250,
      rad: 10,
      amt: 9000
    },
    {
      x: 2055,
      colour: "#BF802F",
      y1: 0,
      y2: 300,
      rad: 10,
      amt: 25000
    }
  ]
  );

  const thisYear = new Date().getFullYear()

  const initialiseData = () => {
    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 ) => {
      // console.log("RANGE", accessor, range)
      return d3.scaleLinear()
        .domain( d3.extent( arr, accessor ) )
        .range( range )
        .nice()
    }

    //SCALES//
    //-------------------------------------------------------------------------------------------------//

    const scX = makeScale( data, d => d.x, [0, pxX - 200]);
    const scY = d3.scaleLinear()
      .domain([0, 100])
      .range( [0, 100] );

    const rad = d3.scaleLinear() //scale for radius of small dots on xaxis
      .domain(d3.extent(data, d => d.rad))
      .range([3, 10]);
    
    const amt = d3.scaleLinear() //scale for radius of large circle      .domain(d3.extent(data, d => d.amt))
      .domain(d3.extent(data, d => d.amt))
      .range([20, 150]);
    
    //-------------------------------------------------------------------------------------------------//
    //END OF SCALES//
    
    //stacks small dots on x axis
    for (let dotindex=0; dotindex<data.length; dotindex++) {
      if (data[dotindex - 1]) {
        if (data[dotindex - 1].x === data[dotindex].x) {
          data[dotindex].y1 = data[dotindex -1].y1 -20
        }
      }
    }

    //creates array of multiples of ten for x axis labels
    let tickTens = [];
    for (let i=thisYear; i<maxYear; i++) {
      if (i % 10 === 0) {
        tickTens.push(i)
      }
    }
    
    //maps array of multiples of ten to values at ticks on x axis
    const g = d3.axisBottom( scX ).tickValues(
      tickTens.map((tickVal) => {
        return tickVal
      })
    )

      //Groups data into arrays by goal year
      const _data = data.reduce(
          (r, v, _, __, k = v["x"]) => ((r[k] || (r[k] = [])).push(v), r),
          []
        )

      //CREATE X AXIS//
      //---------------------------------------------------------------------------//

      svg.append( "g" )
      .attr( "transform", "translate(" + 50 + "," + (pxY - 200) + ")")
      .call( g )
      .selectAll(".tick text")
      .attr("fill", "#7A7A7A")

      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")

      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.y1))
        .attr("r", d => rad(d.rad));

      //END OF CREATE X AXIS//
      //---------------------------------------------------------------------------//


      //CREATE LARGE CIRCLES//
      //---------------------------------------------------------------------------//
      const ticked = () => {
        goalAmounts
          .attr("cx", (d => (2 * d.x)))
          .attr("cy", d => d.y2);
      }

      let i = 0;

      const goalAmounts = svg.selectAll("circle .circles")
        .append( "g" )
        .attr("class", "goalAmounts")
        .data(data.sort(function(a, b) {
          return a.amt - b.amt;
      }).reverse())
        .enter()
        .append("circle")
        .attr( "transform", "translate(" + 650 + "," + 0 + ")")
        .attr("fill", d => {
          return d.colour}
        )
        .attr("cx", d => (d.x))
        .attr("cy", (d, index) => {
          let _y = scY(d.y2)
          if (_data[d.x].length > 1 && _data[i-1]) {
            i++
            _y =  _data[d.x][i -1].y2
          } else {
            i = 0
          }
          return _y
        })
        .attr("r", d => amt(d.amt));

      d3.forceSimulation(data)
      // .force('charge', d3.forceManyBody().strength(-200))
      .force('x', scX(d3.forceX().x(function(d) {
        return d.x
      })))
      .force("y", d3.forceY(d => (d.y2 * Math.random())))
      .force('collision', d3.forceCollide().radius(d => amt(d.amt/2)))
      .on("tick", ticked);

    //END OF CREATE LARGE CIRCLES//
    //---------------------------------------------------------------------------//
      
  }

  

  useEffect(() => {
    if (data) {
      setMaxYear(Math.max.apply(Math, data.map(function(o) { return o.x; })))
    }

    if (maxYear) {
      initialiseData();
    }

  }, [data,maxYear])

  return (
    <div className="App">
      <svg id="demo1" width="1200" height="700" style={{background: "white"}}/>
    </div>
  );
}

export default App;
import React,{useState,useffect}来自“React”;
从“d3”导入*作为d3;
导入“/App.css”;
函数App(){
const[maxYear,setMaxYear]=useState();
const[data,setData]=useState(
[{
x:2020,
颜色:“69306D”,
y1:0,
y2:50,
拉德:10,
金额:2000
},
{
x:2020,
颜色:“247BA0”,
y1:0,
y2:100,
拉德:10,
金额:5000
},
{
x:2020,
颜色:“3F762C”,
y1:0,
y2:150,
拉德:10,
金额:7500
},
{
x:2020,
颜色:“F25F5C”,
y1:0,
y2:200,
拉德:10,
金额:5000
},
{
x:2022,
颜色:“0C3957”,
y1:0,
y2:250,
拉德:10,
金额:9000
},
{
x:2055,
颜色:“BF802F”,
y1:0,
y2:300,
拉德:10,
金额:25000
}
]
);
const thiswear=新日期().getFullYear()
常量initialiseData=()=>{
const svg=d3.选择(“svg”);
常量pxX=svg.attr(“宽度”);
常量pxY=svg.attr(“高度”);
设tickLabelOffset=170;
让minDotX=Math.min.apply(Math,data.map(函数(o){return o.y1;}))
如果(minDotX<-20){
tickLabelOffset+=minDotX+20;
}
常量makeScale=(arr、存取器、范围)=>{
//console.log(“范围”、访问器、范围)
返回d3.scaleLinear()
.域(d3.范围(arr、访问器))
.射程(射程)
.尼斯
}
//天平//
//-------------------------------------------------------------------------------------------------//
constscx=makeScale(数据,d=>d.x[0,pxX-200]);
常数scY=d3.scaleLinear()
.domain([01100])
.范围([01100]);
const rad=d3.scaleLinear()//X轴上小点半径的比例
.域(d3.范围(数据,d=>d.rad))
.范围([3,10]);
const amt=d3.scaleLinear()//大圆半径的刻度。域(d3.extent(数据,d=>d.amt))
.域(d3.范围(数据,d=>d.amt))
.范围([20150]);
//-------------------------------------------------------------------------------------------------//
//天平末端//
//在x轴上堆叠小点
对于(让dotindex=0;dotindex((r[k]| |(r[k]=[]))。推(v),r),
[]
)
//创建X轴//
//---------------------------------------------------------------------------//
svg.append(“g”)
.attr(“转换”、“翻译”(“+50+”,“+(pxY-200)+”))
.电话(g)
.selectAll(“.tick text”)
.attr(“填充”,“#7a7a”)
svg.selectAll(“.domain”)
.attr(“笔划”,“bdbd”)
.attr(“笔划宽度”,“2px”)
.attr(“转换”、“转换”(“+50+”,“+150+”)
svg.selectAll(“.tick line”)
.attr(“笔划”,“bdbd”)
.attr(“笔划宽度”,“4px”)
.attr(“转换”、“转换”(“+50+”,“+150+”)
svg.selectAll(“.tick text”)
.attr(“字体大小”,20)
.attr(“转换”、“平移”(“+50+”,“+tickLabelOffset+”))
.attr(“字体大小”、“粗体”)
.attr(“dy”,“0.5em”)
svg.selectAll(“圆圈”)
.数据(数据)
.输入()
.附加(“g”)
.attr(“类”、“圆”)
.附加(“圆圈”)
.attr(“转换”、“转换”(“+100+”,“+650+”)
.attr(“填充”、“白色”)
.attr(“笔划”,d=>d.color)
.attr(“笔划宽度”,“2px”)
.attr(“cx”,d=>scX(d.x))
.attr(“cy”,d=>scY(d.y1))
.attr(“r”,d=>rad(d.rad));
//创建X轴的结束//
//---------------------------------------------------------------------------//
//创建大圆圈//
//---------------------------------------------------------------------------//
常量勾选=()=>{
goalAmounts
.attr(“cx”,(d=>(2*d.x)))
.attr(“cy”,d=>d.y2);
}
设i=0;
const goalAmounts=svg.selectAll(“circle.circles”)
.附加(“g”)
.attr(“类别”、“目标数量”)
.数据(数据.排序(函数a,b){
返回a.金额-b.金额;
}).reverse())
.输入()
.附加(“圆圈”)
.attr(“转换”、“转换”(“+650+”,“+0+”)
.attr(“填充”,d=>{
返回d.color}
)
.attr(“cx”,d=>(d.x))
.attr(“cy”,(d,索引)=>{
设_y=scY(d.y2)
如果(_数据[d.x].长度>1&&u数据[i-1]){
我++
_y=_数据[d.x][i-1].y2
}否则{
i=0
}
返回y
})
.attr(“r”,d=>amt(d.amt));
d3.力模拟(数据)
//.force('电荷',d3.forceManyBody().强度(-200))
.force('x',scX(d3.forceX().x)函数(d){
返回d.x
})))
.force(“y”,d3.forceY(d=>(d.y2*Math.random()))
.force('collision',d3.forceCollide().radius(d=>amt(d.amt/2)))
。在(勾选)上;
//结束创建大圆圈//
//---------------------------------------------------------------------------//
}
useffect(()=>{
如果(数据){
setMaxYear(数学,最大值,应用),
 import React, {useState, useEffect} from 'react';
import * as d3 from 'd3';
import './App.css';

function App() {
  const [maxYear, setMaxYear] = useState();
  const [data, setData] = useState(
   [{
      x1: 2020,
      colour: "#69306D",
      y1: 0,
      y2: 50,
      rad: 10,
      amt: 2000
    },
    {
      x1: 2021,
      colour: "#247BA0",
      y1: 0,
      y2: 100,
      rad: 10,
      amt: 5000
    },
    {
      x1: 2030,
      colour: "#3F762C",
      y1: 0,
      y2: 150,
      rad: 10,
      amt: 7500
    },
    {
      x1: 2020,
      colour: "#F25F5C",
      y1: 0,
      y2: 200,
      rad: 10,
      amt: 5000
    },
    {
      x1: 2022,
      colour: "#0C3957",
      y1: 0,
      y2: 250,
      rad: 10,
      amt: 9000
    },
    {
      x1: 2055,
      colour: "#BF802F",
      y1: 0,
      y2: 300,
      rad: 10,
      amt: 25000
    }
  ]
  );

  const thisYear = new Date().getFullYear()

  const initialiseData = () => {
    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 ) => {
      // console.log("RANGE", accessor, range)
      return d3.scaleLinear()
        .domain( d3.extent( arr, accessor ) )
        .range( range )
        .nice()
    }

    //SCALES//
    //-------------------------------------------------------------------------------------------------//

    const scX = makeScale( data, d => d.x1, [0, pxX - 200]);
    const scX2 = d3.scaleLinear()
    .domain([2020, 2080])
    .range( [0, pxX - 200] );
    const scY = d3.scaleLinear()
      .domain([0, 100])
      .range( [0, 100] );

    const rad = d3.scaleLinear() //scale for radius of small dots on xaxis
      .domain(d3.extent(data, d => d.rad))
      .range([3, 10]);
    
    const amt = d3.scaleLinear() //scale for radius of large circle      .domain(d3.extent(data, d => d.amt))
      .domain(d3.extent(data, d => d.amt))
      .range([20, 150]);
    
    //-------------------------------------------------------------------------------------------------//
    //END OF SCALES//
    
    //stacks small dots on x axis
    let _newData = data;
    for (let dotindex=0; dotindex<data.length; dotindex++) {
      if (_newData[dotindex - 1]) {
        if (_newData[dotindex - 1].x1 === _newData[dotindex].x1) {
          _newData[dotindex].y1 = _newData[dotindex -1].y1 -20
        }
      }
    }

    setData(_newData)

    console.log("DATA: ", data)

    //creates array of multiples of ten for x axis labels
    let tickTens = [];
    for (let i=thisYear; i<maxYear; i++) {
      if (i % 10 === 0) {
        tickTens.push(i)
      }
    }
    
    //maps array of multiples of ten to values at ticks on x axis
    const g = d3.axisBottom( scX ).tickValues(
      tickTens.map((tickVal) => {
        return tickVal
      })
    )

      //Groups data into arrays by goal year
      const _data = data.reduce(
          (r, v, _, __, k = v["x"]) => ((r[k] || (r[k] = [])).push(v), r),
          []
        )

      //CREATE X AXIS//
      //---------------------------------------------------------------------------//

      svg.append( "g" )
      .attr( "transform", "translate(" + 50 + "," + (pxY - 200) + ")")
      .call( g )
      .selectAll(".tick text")
      .attr("fill", "#7A7A7A")

      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")

      svg.selectAll("circle")
        .data(data)
        .enter()
        .append("g")
        .attr("class", "xDots")
        .append("circle")
        .attr( "transform", "translate(" + 100 + "," + 650 + ")")
        .attr("fill", "white")
        .attr("stroke", d => d.colour)
        .attr("stroke-width", "2px")
        .attr("cx", d => scX(d.x1))
        .attr("cy", d => scY(d.y1))
        .attr("r", d => rad(d.rad));

      //END OF CREATE X AXIS//
      //---------------------------------------------------------------------------//


      //CREATE LARGE CIRCLES//
      //---------------------------------------------------------------------------//
      const ticked = () => {
        goalAmounts
          .attr("cx", (d => d.x))
          .attr("cy", d => d.y);
      }

      let i = 0;

      const goalAmounts = svg.selectAll()
        .data(data.sort(function(a, b) {
          return a.amt - b.amt;
      }).reverse())
        .enter()
        .append("g")
        .attr("class", "goals")
        .append("circle")
        .attr( "transform", "translate(" + 200 + "," + 100 + ")")
        .attr("fill", d => {
          return d.colour}
        )
        .attr("cx", d => {
          // console.log("DX1, ", d.x1,  (d.x1)
          return d.x1
        })
        .attr("cy", (d, index) => {
          let _y = scY(d.y2)
      }
        )
        .attr("r", d => amt(d.amt));

      d3.forceSimulation(data)
      // .force('charge', d3.forceManyBody().strength(-20))
      .force('x', (d3.forceX().x(function(d) {
        return scX2(d.x1)
      })))
      .force("y", d3.forceY(d => (d.y2 * Math.random())))
      .force('collision', d3.forceCollide().radius(d => amt(d.amt) + 10))
      .on("tick", ticked);

    //END OF CREATE LARGE CIRCLES//
    //---------------------------------------------------------------------------//
      
  }

  

  useEffect(() => {
    if (data) {
      console.log("DATA: ", data)
      setMaxYear(Math.max.apply(Math, data.map(function(o) { return o.x1; })))
    }

    if (maxYear) {
      console.log("MAXYEAR", maxYear)
      initialiseData();
    }

  }, [data, maxYear])

  return (
    <div className="App">
      <svg id="demo1" width="1200" height="700" style={{background: "white"}}/>
    </div>
  );
}

export default App;