我正在开发一个使用 ReactJS 的网络应用程序。我在 React 组件中使用 D3 渲染了一张世界地图。它具有基本功能,例如每当单击世界地图上的国家/地区时调整 geoMercator().fitSize()(因此“放大”)。但是,我希望用户能够通过滚动鼠标滚轮来放大自定义数量,但我不知道该怎么做。在网上搜索时,我遇到了 d3 缩放功能,但示例都是通过调整比例来完成的,但我不知道如何将其应用到 d3-geo 上,它似乎没有比例,只有投影。
到目前为止,这是我的代码
function GeoChart() {
const svgRef = useRef();
const wrapperRef = useRef();
const [dimensions, setDimensions] = useState(useResizeObserver(wrapperRef));
const [selectedCountry, setSelectedCountry] = useState(null);
useEffect(() => {
const svg = select(svgRef.current);
const { width, height } = wrapperRef.current.getBoundingClientRect();
const minProp = leadCount ? min(leadCount, count => count.count) : null
const maxProp = leadCount ? max(leadCount, count => count.count) : null
const colorScale = scaleLinear()
.domain([minProp, maxProp])
.range(["#ccc", "red"]);
// projects geo-coordinates on a 2D plane
const projection = geoMercator()
.fitSize([width, height], selectedCountry || data)
.precision(100);
// takes geojson data,
// transforms that into the d attribute of a path element
const pathGenerator = geoPath().projection(projection);
// render each country
svg
.selectAll(".country")
.data(data.features)
.join("path")
.on("click", feature => {
console.log(selectedCountry)
setSelectedCountry(selectedCountry === feature ? null : feature);
})
.attr("class", "country")
.transition()
.attr("fill", feature => findLeadCount(feature.properties.name))
.attr("d", feature => pathGenerator(feature))
}, [data, dimensions, selectedCountry]);
return (
<div ref={wrapperRef} style={{ marginBottom: "2rem" }}>
<svg id="geo-chart" ref={svgRef}></svg>
</div>
) }
export default GeoChart;
Run Code Online (Sandbox Code Playgroud)
感谢所有帮助,我是 D3 的新手,因此可能有一些我不知道该怎么做的东西,请帮助指导我正确的方向,谢谢!
And*_*eid 13
我提供两个通用的 D3 解决方案;React 的适配应该相当简单。通过保持通用性,答案和片段更加清晰,尽管答案仍然很冗长......
坐标
在处理地理投影和缩放时,我们要处理几个坐标系:
svg变换属性)可以通过投影功能使用适当的平移和缩放,这样就不需要 SVG/Canvas 转换。例如,如果需要进一步放大地图,则将 D3 投影比例或 SVG/Canvas 比例加倍具有相同的效果。因此,我们可以通过确保设置投影参数而无需设置 SVG/Canvas 变换来有效地消除对第三坐标系的考虑。
如果我们想避免使用 SVG/Canvas 变换,我们将进行语义缩放:我们将根据更新的投影重新绘制所有数据。这让投影完成所有工作。
如果我们想操纵 SVG/Canvas 变换来实现缩放,我们将进行几何缩放。这仅需要在初始绘制要素时进行投影,然后使用 SVG/Canvas 变换来移动已绘制的要素并根据需要调整其大小。
两者都有不同的优点和缺点。我不会在这里谈论他们,但我会展示如何实现这两个目标。
语义缩放
对于您的用例,这可能是更容易实施的解决方案。
我们可以用来d3.zoom()跟踪当前的平移和缩放:
let zoom = d3.zoom()
.on("zoom", function() {
let t = d3.event.transform; // get current zoom state
projection.scale(t.k).translate([t.x,t.y]); // set scale and translate of projection.
features.attr("d", path) // redraw the features
})
svg.call(zoom);
Run Code Online (Sandbox Code Playgroud)
上面的内容将让用户通过使用鼠标平移或缩放来与功能进行交互(通过投影)。D3.zoom 跟踪累积缩放平移和比例。我们将使用它来设置投影参数,然后使用更新的投影重新绘制所有特征,而不是使用它的值设置类似 SVG 变换的内容。
但是,我们需要设置缩放的初始值。缩放的初始比例为 1,对于 d3.geoMercator 投影,这将形成约 6x6 像素的世界。我们可以设置初始缩放状态selection.call(zoom.transform, transform):
// Set up an initial projection translate and scale.
svg.call(zoom.transform, d3.zoomIdentity.translate(width/2,height/2).scale(width/Math.PI/2));
Run Code Online (Sandbox Code Playgroud)
平移代表视口的中心。对于 D3 墨卡托投影,比例通常是经度的一个弧度所分布的像素数。墨卡托的默认中心是 [0,0],因此上面的代码将地图居中并将其缩放到视口。
zoom.transform触发缩放事件,并且重要的是更新缩放状态,以便投影和缩放对齐。
最棘手的部分是使用fitSize()- 这会修改投影,但不会修改缩放状态。但是,fitSize()仅修改投影的平移和比例,而不修改其中心。因此,我们可以简单地提取当前比例并进行平移,并使用此数据以编程方式触发缩放事件:
function centerOnFeature(feature) {
projection.fitSize([width,height],feature);
var k = projection.scale();
var t = projection.translate();
svg.call(zoom.transform, d3.zoomIdentity.translate(...t).scale(k));
}
Run Code Online (Sandbox Code Playgroud)
请注意,这确实设置了两次投影参数:一次使用 fitSize,一次在 Zoom 事件函数中,这是可以避免的,但性能损失应该不存在,要求的是特征的绘制。
以下是正在工作的这三个代码块(对最后一个代码块稍作调整):
let zoom = d3.zoom()
.on("zoom", function() {
let t = d3.event.transform; // get current zoom state
projection.scale(t.k).translate([t.x,t.y]); // set scale and translate of projection.
features.attr("d", path) // redraw the features
})
svg.call(zoom);
Run Code Online (Sandbox Code Playgroud)
// Set up an initial projection translate and scale.
svg.call(zoom.transform, d3.zoomIdentity.translate(width/2,height/2).scale(width/Math.PI/2));
Run Code Online (Sandbox Code Playgroud)
function centerOnFeature(feature) {
projection.fitSize([width,height],feature);
var k = projection.scale();
var t = projection.translate();
svg.call(zoom.transform, d3.zoomIdentity.translate(...t).scale(k));
}
Run Code Online (Sandbox Code Playgroud)
几何缩放
这可能会导致更多混乱,因为很容易混合语义和几何,但应该很容易实现。
投影将使用一次:最初绘制特征。使用fitSize()缩放某些要素将修改投影的平移和比例。由于我们使用 SVG/Canvas 变换来适当缩放地图并使地图居中,因此我们无法使用此修改后的投影来绘制新要素:这将导致应用缩放和平移两次。这种情况下的解决方案是拥有一个不受 fitSize() 干扰的重复比例。
再次,让我们使用它d3.zoom()来跟踪当前的平移和缩放,但这次使用它来修改 SVG 上的变换:
let zoom = d3.zoom()
.on("zoom", function() {
g.attr("transform", d3.event.transform); // apply the current zoom to a parent holding our features
})
svg.call(zoom);
Run Code Online (Sandbox Code Playgroud)
同样,上述内容将让用户通过使用鼠标平移或缩放来与功能进行交互。
我们不需要设置初始缩放状态,只需在设置初始投影参数后绘制我们想要的特征即可:
let projection = d3.geoMercator()
.translate([width/2,height/2])
.scale(width/Math.PI/2);
let path = d3.geoPath(projection);
features.attr("d", path);
Run Code Online (Sandbox Code Playgroud)
并且,要以编程方式缩放到特定国家或功能,我们可以使用:
function centerOnFeature(feature) {
projection.fitSize([width,height],feature);
let k = projection.scale() / t0.k; // relative to initial scale.
let x = projection.translate()[0] - t0.x * k; // relative to initial scale.
let y = projection.translate()[1] - t0.y * k; // relative to initial scale.
svg.call(zoom.transform, d3.zoomIdentity.translate(x,y).scale(k));
}
Run Code Online (Sandbox Code Playgroud)
在这里,我用来t0跟踪初始投影平移和缩放。我们想知道坐标值与初始投影的相对变化,然后我们获取该相对变化并将其应用到缩放中selection.call(zoom.transform,...。
以下是正在工作的三个代码块(稍作调整 - 例如考虑缩放时的笔画宽度):
var width = 480;
var height = 480;
var svg = d3.select("svg");
var projection = d3.geoMercator();
var path = d3.geoPath(projection);
d3.json("https://d3js.org/world-110m.v1.json").then(function(world) {
// Draw the world.
let countries = topojson.feature(world, world.objects.countries).features;
let features = svg.selectAll("path")
.data(countries)
.enter()
.append("path")
// Let the zoom take care of modifying the projection:
let zoom = d3.zoom()
.on("zoom", function() {
let t = d3.event.transform;
projection.scale(t.k).translate([t.x,t.y]);
features.attr("d", path)
})
svg.call(zoom);
// Set up an initial projection translate and scale.
svg.call(zoom.transform, d3.zoomIdentity.translate(width/2,height/2).scale(width/Math.PI/2));
// Let us click on a country:
features.on("click", function(d) {
projection.fitSize([width,height],d);
var k = projection.scale();
var t = projection.translate();
svg.call(zoom.transform, d3.zoomIdentity.translate(...t).scale(k));
})
// Some buttons to programatically set the translate and scale:
d3.select("div")
.selectAll(null)
.data([{label:"Angola",id:"024"},{label:"New Zealand",id:"554"}])
.enter()
.append("button")
.text(function(d) { return d.label; })
.on("click", function(d) {
projection.fitSize([width,height],getCountrybyID(d.id,countries));
var k = projection.scale();
var t = projection.translate();
svg.call(zoom.transform, d3.zoomIdentity.translate(...t).scale(k));
})
});
// Helper function:
function getCountrybyID(id,countries) {
for(var i = 0; i < countries.length; i++) {
if(id == countries[i].id) return countries[i];
}
}Run Code Online (Sandbox Code Playgroud)
path {
stroke: #ccc;
stroke-width: 1px;
fill: #333;
}Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<div>.</div>
<svg width="480" height="480"></svg>Run Code Online (Sandbox Code Playgroud)
混合方法
最好避免这种情况。
| 归档时间: |
|
| 查看次数: |
3623 次 |
| 最近记录: |