修改 SVG 地图上标记的标记
Modify the markup for markers on the SVG map
我是 React 和 d3 的新手,我正在尝试使用 D3 v6.2 和 ReactJS 实现一个简单的 SVG 地图,这将指示一些城市的位置。每个城市都用圆圈表示,如下所示:
我需要更改此标记以放置带有城市名称的图钉(如果可能,请使用 div),如图所示:
但是点的符号形状由 D3-path 本身决定,我找不到任何可以更改或替换为其他标记的内容。
注意:我试图在点的 path
标签内放置一个 div
,但我意识到它们没有被渲染,我唯一能做的就是改变pointRadius ()
标记的半径,但我不想那样做。
代码:
import React, { useEffect, useRef, useState } from 'react';
import { select, geoPath, geoMercator } from "d3";
import useResizeObserver from 'use-resize-observer';
import drawMap from './mapa.json';
import dataMap from './city.json';
const svgRef = useRef(null);
const wrapperRef = useRef(null);
const { widthRef, heightRef } = useResizeObserver({ wrapperRef });
function map(){
useEffect(() => {
const svg = select(svgRef.current);
const { width, height } = wrapperRef.current.getBoundingClientRect();
const projection = geoMercator().fitSize([width, height], selectedEstados || drawMap);
let pathGenerator = geoPath().projection(projection).pointRadius(4);
console.log("path: " + pathGenerator)
svg.selectAll(".estado")
.data(drawMap.features)
.join("path")
.attr("class", "estado")
.attr("d", features => pathGenerator(features));
svg.selectAll(".city")
.data(dataMap.features)
.join("path")
.attr("class", "city")
.attr("d", features => pathGenerator(features))
}, [drawMap, wrapperRef, widthRef, heightRef])
return(
<div className='map' ref={wrapperRef}>
<svg ref={svgRef}></svg>
</div>
)
}
city.json
{
"type": "FeatureCollection",
"name": "city",
"crs": {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
}
},
"features": [
{
"type": "Feature",
"properties": {
"municipio": "sapé"
},
"geometry": {
"type": "Point",
"coordinates": [
-41.821476100786512,
-8.282815195947387
]
}
},
{
"type": "Feature",
"properties": {
"municipio": "muito distante"
},
"geometry": {
"type": "Point",
"coordinates": [
-41.065329661162544,
-7.183034162673664
]
}
}
]
}
mapa.json
有多种选择,最简单的可能就是使用 SVG 路径绘制引脚形状。
路径生成器不会在这里帮助我们。相反,我们将直接使用投影,projection([longitude,latitude])
将 return [x,y]
以像素为单位。这个投影的 x,y 坐标可以让我们将标记锚定到地图上的正确位置。您方便地使用的 geojson 有一个 属性 包含经度和纬度。为了简单起见,下面我只使用一个简单的非geojson数组。
下面我将每个城市绘制为具有适当翻译的 g
元素,这样我就可以放置与城市相关的文本、路径和圆形元素:
var svg = d3.select("body").append("svg").attr("width", 400);
var projection = d3.geoMercator();
var cityData = [{name:"Vancouver",longlat:[-123,49]},{name:"Anchorage",longlat:[-150,61]}];
var cities = svg.selectAll(null)
.data(cityData)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate("+projection(d.longlat)+")";
})
// Two different paths for comparison:
cities.append("path")
.attr("d", (d,i) => ["M0,0 Q0 -20 -40 -20 L-40 -40L40 -40L40 -20 Q 0 -20 0 0",
"M0,0 L-40 -20L-40 -40L40 -40L40 -20Z"][i] )
.attr("fill","#ddd")
.attr("stroke","black")
.attr("stroke-width",1);
cities.append("text")
.attr("dy", -25)
.text( function(d) { return d.name; })
.style("text-anchor","middle");
// optional circle
cities.append("circle")
.attr("r", (d,i) => [2,6][i])
.attr("fill", (d,i) => ["black","white"][i])
.attr("stroke-width",(d,i) => [0,1][i])
circle { stroke:black; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
在这种情况下,路径是固定宽度的:恰好名称相对较好地填充了引脚。您可以确定图钉的适当宽度并使用它来创建路径。
如果您确实需要 div,则需要使用 SVG foreignObject 元素。您没有看到任何 div 呈现的原因是 SVG 在其命名空间中没有 div 元素,HTML 和 SVG 元素不可互换。 foreignObject 让我们在 SVG 中放置 HTML 个对象。 注意:IE 不支持 ForeignObjects.
这是一个在 foreignObject 中带有 div 的快速模型:
var svg = d3.select("body").append("svg").attr("width", 400);
var projection = d3.geoMercator();
var cityData = [{name:"Vancouver",longlat:[-123,49]},{name:"Anchorage",longlat:[-150,61]}];
var cities = svg.selectAll(null)
.data(cityData)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate("+projection(d.longlat)+")";
})
var div = cities.append("foreignObject")
.attr("x", -25)
.attr("y", -35)
.attr("width", 100)
.attr("height", 100)
.append("xhtml:div")
.attr("class","box arrow-bottom")
.text(d=>d.name);
cities.append("circle")
.attr("r", 4)
.attr("fill","orange");
/* css from https://codeconvey.com/css-message-box-with-arrow/ */
.box {
height: auto;
background-color: black;
color: #fff;
position: relative;
}
.box.arrow-bottom:after {
content: "";
position: absolute;
left: 10px;
bottom: -15px;
border-top: 15px solid black;
border-right: 15px solid transparent;
border-left: 15px solid transparent;
border-bottom: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
以上只是手动定位,如果有更好的css样式示例可以提供更精确的定位点。
第三种选择是使用绝对坐标将 divs 定位在地图 SVG 上,这需要考虑 SVG 在页面上的位置。它还使 zooming/panning 稍微复杂一些。
我是 React 和 d3 的新手,我正在尝试使用 D3 v6.2 和 ReactJS 实现一个简单的 SVG 地图,这将指示一些城市的位置。每个城市都用圆圈表示,如下所示:
我需要更改此标记以放置带有城市名称的图钉(如果可能,请使用 div),如图所示:
但是点的符号形状由 D3-path 本身决定,我找不到任何可以更改或替换为其他标记的内容。
注意:我试图在点的 path
标签内放置一个 div
,但我意识到它们没有被渲染,我唯一能做的就是改变pointRadius ()
标记的半径,但我不想那样做。
代码:
import React, { useEffect, useRef, useState } from 'react';
import { select, geoPath, geoMercator } from "d3";
import useResizeObserver from 'use-resize-observer';
import drawMap from './mapa.json';
import dataMap from './city.json';
const svgRef = useRef(null);
const wrapperRef = useRef(null);
const { widthRef, heightRef } = useResizeObserver({ wrapperRef });
function map(){
useEffect(() => {
const svg = select(svgRef.current);
const { width, height } = wrapperRef.current.getBoundingClientRect();
const projection = geoMercator().fitSize([width, height], selectedEstados || drawMap);
let pathGenerator = geoPath().projection(projection).pointRadius(4);
console.log("path: " + pathGenerator)
svg.selectAll(".estado")
.data(drawMap.features)
.join("path")
.attr("class", "estado")
.attr("d", features => pathGenerator(features));
svg.selectAll(".city")
.data(dataMap.features)
.join("path")
.attr("class", "city")
.attr("d", features => pathGenerator(features))
}, [drawMap, wrapperRef, widthRef, heightRef])
return(
<div className='map' ref={wrapperRef}>
<svg ref={svgRef}></svg>
</div>
)
}
city.json
{
"type": "FeatureCollection",
"name": "city",
"crs": {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
}
},
"features": [
{
"type": "Feature",
"properties": {
"municipio": "sapé"
},
"geometry": {
"type": "Point",
"coordinates": [
-41.821476100786512,
-8.282815195947387
]
}
},
{
"type": "Feature",
"properties": {
"municipio": "muito distante"
},
"geometry": {
"type": "Point",
"coordinates": [
-41.065329661162544,
-7.183034162673664
]
}
}
]
}
mapa.json
有多种选择,最简单的可能就是使用 SVG 路径绘制引脚形状。
路径生成器不会在这里帮助我们。相反,我们将直接使用投影,projection([longitude,latitude])
将 return [x,y]
以像素为单位。这个投影的 x,y 坐标可以让我们将标记锚定到地图上的正确位置。您方便地使用的 geojson 有一个 属性 包含经度和纬度。为了简单起见,下面我只使用一个简单的非geojson数组。
下面我将每个城市绘制为具有适当翻译的 g
元素,这样我就可以放置与城市相关的文本、路径和圆形元素:
var svg = d3.select("body").append("svg").attr("width", 400);
var projection = d3.geoMercator();
var cityData = [{name:"Vancouver",longlat:[-123,49]},{name:"Anchorage",longlat:[-150,61]}];
var cities = svg.selectAll(null)
.data(cityData)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate("+projection(d.longlat)+")";
})
// Two different paths for comparison:
cities.append("path")
.attr("d", (d,i) => ["M0,0 Q0 -20 -40 -20 L-40 -40L40 -40L40 -20 Q 0 -20 0 0",
"M0,0 L-40 -20L-40 -40L40 -40L40 -20Z"][i] )
.attr("fill","#ddd")
.attr("stroke","black")
.attr("stroke-width",1);
cities.append("text")
.attr("dy", -25)
.text( function(d) { return d.name; })
.style("text-anchor","middle");
// optional circle
cities.append("circle")
.attr("r", (d,i) => [2,6][i])
.attr("fill", (d,i) => ["black","white"][i])
.attr("stroke-width",(d,i) => [0,1][i])
circle { stroke:black; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
在这种情况下,路径是固定宽度的:恰好名称相对较好地填充了引脚。您可以确定图钉的适当宽度并使用它来创建路径。
如果您确实需要 div,则需要使用 SVG foreignObject 元素。您没有看到任何 div 呈现的原因是 SVG 在其命名空间中没有 div 元素,HTML 和 SVG 元素不可互换。 foreignObject 让我们在 SVG 中放置 HTML 个对象。 注意:IE 不支持 ForeignObjects.
这是一个在 foreignObject 中带有 div 的快速模型:
var svg = d3.select("body").append("svg").attr("width", 400);
var projection = d3.geoMercator();
var cityData = [{name:"Vancouver",longlat:[-123,49]},{name:"Anchorage",longlat:[-150,61]}];
var cities = svg.selectAll(null)
.data(cityData)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate("+projection(d.longlat)+")";
})
var div = cities.append("foreignObject")
.attr("x", -25)
.attr("y", -35)
.attr("width", 100)
.attr("height", 100)
.append("xhtml:div")
.attr("class","box arrow-bottom")
.text(d=>d.name);
cities.append("circle")
.attr("r", 4)
.attr("fill","orange");
/* css from https://codeconvey.com/css-message-box-with-arrow/ */
.box {
height: auto;
background-color: black;
color: #fff;
position: relative;
}
.box.arrow-bottom:after {
content: "";
position: absolute;
left: 10px;
bottom: -15px;
border-top: 15px solid black;
border-right: 15px solid transparent;
border-left: 15px solid transparent;
border-bottom: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
以上只是手动定位,如果有更好的css样式示例可以提供更精确的定位点。
第三种选择是使用绝对坐标将 divs 定位在地图 SVG 上,这需要考虑 SVG 在页面上的位置。它还使 zooming/panning 稍微复杂一些。