Javascript d3 forceX未将圆定位在其x轴值附近
我的d3图看起来很棒-除了一件事之外,它很漂亮-我的圆的x位置似乎与数组中给定的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轴,表示确切的日期,它们的颜色与较大圆圈的颜色相匹配,其半径在较大的范围内变化
{
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;