修改SVG地图上标记的标记语言标记。

3

我是React和D3的新手,想要用D3 v6.2和ReactJS实现一个简单的SVG地图,用来标示一些城市的位置。目前每个城市都用圆形表示,如下图所示:

enter image description here

我需要将这种标记改成像下面这张图片一样带有城市名称的图钉(如果可能使用div):

enter image description here

但是点的符号形状是由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

在此处查看地理JSON地图

1个回答

2

有许多选择,最简单的可能就是使用SVG路径来绘制您的标记形状。

路径生成器在这里无法帮助我们。相反,我们将直接使用投影,projection([longitude,latitude])会以像素为单位返回[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样式示例可以提供更精确的位置定位。
第三种选择是使用绝对坐标在地图SVG上定位div,这需要考虑SVG在页面上的位置。它也会使缩放/平移稍微复杂一些。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接