簡體   English   中英

d3 forceX 不將圓圈定位在其 x 軸值附近

[英]d3 forceX not positioning circles near their x axis value

我的 d3 圖看起來很棒 - 除了一件事它很漂亮 - 我的圈子的 x position 似乎與我的數組中給出的 x 值不太對應。

我有一個像這樣的對象數組:

  {
      x: 2020,
      cx: 0,
      colour: "#F25F5C",
      y1: 0,
      y2: 200,
      rad: 10,
      amt: 5000
    }, 

我有兩組圓圈。 第一個位置非常靠近 x 軸並指示確切日期,並且它們的顏色與較大圓圈的顏色匹配,這些圓圈的半徑在較大范圍內變化,我將 d3.force 應用於這些更有效地 position 它們。

問題在於較大的圓圈的中心應該靠近 (x, y2) 給出的數組中的值。 他們不是,但我的 forceX 似乎沒有工作。

這是我的完整代碼:

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;

請查看代碼到目前為止如何使用此沙箱:d3Sandbox

這是我認為的工作版本的代碼?

 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;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM