gpt4 book ai didi

javascript - D3 - 如何在数据值变化时更新力模拟

转载 作者:塔克拉玛干 更新时间:2023-11-02 20:39:52 26 4
gpt4 key购买 nike

我在 D3 中遇到了一个关于力模拟的小问题。

我有代表每个国家/地区从 1998 年到 2008 年的贫困率的数据。它是一个气泡图,分为三组,代表贫穷国家、不贫穷国家和没有信息的国家。

应用最初加载时,会加载 1998 年的数据。但是,我在顶部有一些按钮,单击它们会更改年份,随后气泡会自行重新排列。我所能做的就是在单击按钮时更改变量 year。但是,有些函数和变量在整个代码中使用 year。当 year 更改时,我想重新计算所有依赖于 year

的节点属性和强制参数

这是我的代码。如果您想尝试一下,我已经包含了所有内容。数据文件在本文末尾。

 async function init() {
// Set up the canvas
var height = 1000, width = 2000;
var svg = d3.select("#panel1").append("svg")
.attr("height", height)
.attr("width", width)
.attr("class", "bubblePanel");

var canvas = svg.append("g")
.attr("transform", "translate(0,0)");

// Choose what year to look at, based on button clicks.
var year = "X1998"
d3.select("#b1998").on("click", function() {
year = "X1998"
console.log(year)
// NOTIFY SIMULATION OF CHANGE //
})
d3.select("#b1999").on("click", function() {
year = "X1999"
console.log(year)
// NOTIFY SIMULATION OF CHANGE //
})
d3.select("#b2000").on("click", function() {
year = "X2000"
console.log(year)
// NOTIFY SIMULATION OF CHANGE //
})

// Implement the physics of the elements. Three forces act according to the poverty level (poor, not poor, and no info)
var simulation = d3.forceSimulation()
.force("x", d3.forceX(function(d) {
if (parseFloat(d[year]) >= 10) {
return 1700
} else if (parseFloat(d[year]) === 0) {
return 1000
} else {
return 300
}
}).strength(0.05))
.force("y", d3.forceY(300).strength(0.05))
.force("collide", d3.forceCollide(function(d) {
return radiusScale(d[year])
}));

// Function to pick colour of circles according to region
function pickColor(d) {
if (d === "East Asia & Pacific") {
return "red"
} else if (d === "Europe & Central Asia") {
return "orange"
} else if (d === "Latin America & Caribbean") {
return "yellow"
} else if (d === "Middle East & North Africa") {
return "green"
} else if (d === "North America") {
return "blue"
} else if (d === "South Asia") {
return "indigo"
} else {
return "violet"
}
}

// Set the scales for bubble radius, and text size.
var radiusScale = d3.scaleSqrt().domain([0, 50]).range([20,80]);
var labelScale = d3.scaleSqrt().domain([0,50]).range([10,40]);

// Read the data
await d3.csv("wd3.csv").then(function(data) {
// Assign each data point to a circle that is colored according to region and has radius according to its poverty level
var bubbles = svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", 100)
.attr("cy", 100)
.attr("fill", function(d) {
return pickColor(d.Region)
})
.attr("r", function(d) {
return radiusScale(d[year])
});
// Assign each ddata point to a text element that shows the counry code of the data point. The text is scaled according to the poverty level
var labels = svg.selectAll("text")
.data(data)
.enter().append("text")
.attr("x", 100)
.attr("y", 100)
.attr("dominant-baseline", "central")
.text(function(d) { return d.XCountryCode })
.style("stroke", "black")
.style("text-anchor", "middle")
.style("font-size", function(d) { return labelScale(d[year]); });


// Code to handle the physics of the bubble and the text
simulation.nodes(data)
.on("tick", ticked)
function ticked() {
bubbles.attr("transform", function(d) {
var k = "translate(" + d.x + "," + d.y + ")";
return k;
})
labels.attr("transform", function(d) {
var k = "translate(" + d.x + "," + d.y + ")";
return k;
})

}
});
}

year 发生变化时,每个国家/地区的数据值都会发生变化。我希望我的代码的以下部分得到更新。

节点上的 x 力:国家可以在一年内从贫穷变为另一年不贫穷,因此他们的集群将发生变化

圆圈的半径: 半径代表贫困程度。这些每年都会发生变化,因此单击按钮时圆圈的大小会发生变化

国家标签的坐标:这些标签也附加到数据上。因此,当圆圈上的 x 力导致圆圈移动时,标签也应该移动。

非常感谢您的帮助。

可以找到数据文件here .我不小心将它命名为 povertyCSV,但在代码中,它被引用为“wd3.csv”

最佳答案

如果我正确理解问题:

重新初始化力

提供的用于设置 d3 力参数的函数,例如 forceX 或 forceCollision,在模拟初始化时(当节点最初分配给布局时)每个节点执行一次。一旦模拟开始,这会节省大量时间:我们不会在每个刻度都重新计算力参数。

但是,如果您有一个现有的力布局,并且想用新的 x 值或新的强度修改 forceX,或者用新的半径修改 forceCollision,例如,我们可以重新初始化力来执行重新计算:

 // assign a force to the force diagram:
simulation.force("someForce", d3.forceSomeForce().someProperty(function(d) { ... }) )

// re-initialize the force
simulation.force("someForce").initialize(nodes);

这意味着如果我们有这样的力:

simulation.force("x",d3.forceX().x(function(d) { return fn(d["year"]); }))

然后我们更新变量year,我们需要做的就是:

year = "newValue";

simulation.force("x").initialize(nodes);

定位

如果力被重新初始化(或重新分配),则无需触摸刻度函数:它会根据需要更新节点。标签和圈子将继续正确更新。

此外,非位置性的事物(例如颜色)需要在事件处理程序中更新,该事件处理程序也会重新初始化力。除了半径之外,大多数东西应该通过强制或直接修改元素来更新,而不是两者都更新。

Radius 是一个特例:

  • 使用 d3.forceCollide,半径影响定位
  • 但是,Radius 不需要每次更新都更新。

因此,在更新半径的时候,我们需要更新碰撞力,修改每个圆的r属性。

如果寻找以图形和碰撞力反射(reflect)的半径的平滑过渡,这应该是一个单独的问题。

实现

我借用了您的代码来制作一个相当通用的示例。下面的代码包含一些按钮的以下事件监听器,其中每个按钮的数据是一年:

buttons.on("click", function(d) {
// d is the year:
year = d;

// reheat the simulation:
simulation
.alpha(0.5)
.alphaTarget(0.3)
.restart();

// (re)initialize the forces
simulation.force("x").initialize(data);
simulation.force("collide").initialize(data);

// update altered visual properties:
bubbles.attr("r", function(d) {
return radiusScale(d[year]);
}).attr("fill", function(d) {
return colorScale(d[year]);
})
})

以下代码段使用任意数据,并且由于其大小,可能不允许节点每次都完美地重新组织。为简单起见,位置、颜色和半径都基于同一个变量。最终,它应该解决问题的关键部分:当 year 更改时,我想更新使用 year 设置节点和强制属性的所有内容。

var data = [
{year1:2,year2:1,year3:3,label:"a"},
{year1:3,year2:4,year3:5,label:"b"},
{year1:5,year2:9,year3:7,label:"c"},
{year1:8,year2:16,year3:11,label:"d"},
{year1:13,year2:25,year3:13,label:"e"},
{year1:21,year2:36,year3:17,label:"f"},
{year1:34,year2:1,year3:19,label:"g"},
{year1:2,year2:4,year3:23,label:"h"},
{year1:3,year2:9,year3:29,label:"i"},
{year1:5,year2:16,year3:31,label:"j"},
{year1:8,year2:25,year3:37,label:"k"},
{year1:13,year2:36,year3:3,label:"l"},
{year1:21,year2:1,year3:5,label:"m"}
];

// Create some buttons:
var buttons = d3.select("body").selectAll("button")
.data(["year1","year2","year3"])
.enter()
.append("button")
.text(function(d) { return d; })


// Go about setting the force layout:
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 300);

var radiusScale = d3.scaleSqrt()
.domain([0, 40])
.range([5,30]);

var colorScale = d3.scaleLinear()
.domain([0,10,37])
.range(["#c7e9b4","#41b6c4","#253494"]);

var year = "year1";

var simulation = d3.forceSimulation()
.force("x", d3.forceX(function(d) {
if (parseFloat(d[year]) >= 15) {
return 100
} else if (parseFloat(d[year]) > 5) {
return 250
} else {
return 400
}
}).strength(0.05))
.force("y", d3.forceY(150).strength(0.05))
.force("collide", d3.forceCollide()
.radius(function(d) {
return radiusScale(d[year])
}));

var bubbles = svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", function(d) {
return radiusScale(d[year])
})
.attr("fill", function(d) {
return colorScale(d[year]);
});

var labels = svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
return d.label;
})
.style("text-anchor","middle");

simulation.nodes(data)
.on("tick", ticked)


function ticked() {

bubbles.attr("cx", function(d) {
return d.x;
}).attr("cy", function(d) {
return d.y;
})

labels.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y +5;
})

}

buttons.on("click", function(d) {
// d is the year:
year = d;

simulation
.alpha(0.5)
.alphaTarget(0.3)
.restart();

simulation.force("x").initialize(data);
simulation.force("collide").initialize(data);

bubbles.attr("r", function(d) {
return radiusScale(d[year]);
}).attr("fill", function(d) {
return colorScale(d[year]);
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

关于javascript - D3 - 如何在数据值变化时更新力模拟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57277281/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com