You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Leaflet与D3.js路径动画实现报错:d未定义的解决方法

Fixing "d is undefined" Error in Leaflet + D3 Path Animation

Hey there! Let's break down why you're seeing that "d is undefined" error and get your path animation working properly.

What's Causing the Error?

Your code hits this error for three key reasons:

  1. You're calling a tweenDash function that doesn't exist in your code. This function is critical for creating the stroke-dasharray animation, and without it, D3 can't access the path data (d) needed to calculate the animation.
  2. You're passing GeoJSON Feature objects directly to d3.svg.line(), but this function expects raw coordinate points (not full Feature objects). This means your path isn't being generated correctly in the first place.
  3. Two helper functions from the original tutorial—applyLatLngToLayer and projectPoint—are missing entirely, which breaks how your coordinates are converted to map pixel positions.

Step-by-Step Fixes

1. Add the Missing Helper Functions

These functions handle converting GeoJSON lat/long coordinates to Leaflet's map pixel positions:

// Convert GeoJSON lat/long to Leaflet map pixels
function applyLatLngToLayer(d) {
    var y = d.geometry.coordinates[1];
    var x = d.geometry.coordinates[0];
    return map.latLngToLayerPoint(new L.LatLng(y, x));
}

// D3 projection function to sync with Leaflet's map view
function projectPoint(x, y) {
    var point = map.latLngToLayerPoint(new L.LatLng(y, x));
    this.stream.point(point.x, point.y);
}

2. Define the tweenDash Animation Function

This is the core of the path animation—it calculates the path length and animates the stroke-dasharray property to create the "drawing" effect:

function tweenDash() {
    var l = this.getTotalLength(),
        i = d3.interpolateString("0," + l, l + "," + l);
    return function(t) { return i(t); };
}

3. Fix the Path Data for toLine

Modify how you pass data to the line generator to extract raw coordinates from your GeoJSON Features:

// Update the toLine generator to work with coordinate arrays
var toLine = d3.svg.line()
    .interpolate("linear")
    .x(function(d) { return applyLatLngToLayer({geometry: {coordinates: d}}).x; })
    .y(function(d) { return applyLatLngToLayer({geometry: {coordinates: d}}).y; });

// When setting linePath's d attribute, pass the coordinate array instead of Feature objects
linePath.attr("d", function(d) {
    // Extract coordinates from each Feature in featuresdata
    var coords = d.map(function(feature) {
        return feature.geometry.coordinates;
    });
    return toLine(coords);
});

Full Corrected Code

Here's the complete code with all fixes applied, plus basic CSS to make the animation visible:

<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8" /> 
<script src="https://d3js.org/d3.v3.min.js"></script> 
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js" integrity="sha512-/Nsx9X4HebavoBvEBuyp3I7od5tA0UzAxs+j83KgC8PU0kgB4XiK4Lfe4y4cgBtaRJQEIFCW+oC506aPT2L1zw==" crossorigin=""></script>
<style>
 html, body { height: 100%; width: 100%; }
 body { margin: 0; }
 #map { width: 100%; height: 100%; }
 .lineConnect {
     stroke: #2c3e50;
     stroke-width: 2px;
     fill: none;
 }
 .travelMarker {
     fill: #e74c3c;
     stroke: #fff;
     stroke-width: 1px;
 }
 .waypoints {
     fill: #3498db;
 }
 .locnames {
     font-family: Arial;
     font-size: 12px;
     fill: #2c3e50;
 }
</style>
</head>
<body>
<div id="map"></div>
<script type="text/javascript">
// set up the map
var map = new L.Map('map');
// create the tile layer with correct attribution
var osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
var osmAttrib='Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors';
var osm = new L.TileLayer(osmUrl, {minZoom: 8, maxZoom: 15, attribution: osmAttrib});
// start the map in Donosti
map.setView(new L.LatLng(40.72332345541449, -73.99),14);
map.addLayer(osm);

var svg = d3.select(map.getPanes().overlayPane).append("svg");
var g = svg.append("g").attr("class", "leaflet-zoom-hide");

// ---------------------- Added Helper Functions ----------------------
function applyLatLngToLayer(d) {
    var y = d.geometry.coordinates[1];
    var x = d.geometry.coordinates[0];
    return map.latLngToLayerPoint(new L.LatLng(y, x));
}

function projectPoint(x, y) {
    var point = map.latLngToLayerPoint(new L.LatLng(y, x));
    this.stream.point(point.x, point.y);
}
// -------------------------------------------------------------------

d3.json("points.geojson", function(collection) {
 var featuresdata = collection.features.filter(function(d) {
 return d.properties.id == "route1"
 });

 var transform = d3.geo.transform({ point: projectPoint });
 var d3path = d3.geo.path().projection(transform);

 // ---------------------- Fixed toLine Generator ----------------------
 var toLine = d3.svg.line()
 .interpolate("linear")
 .x(function(d) { return applyLatLngToLayer({geometry: {coordinates: d}}).x })
 .y(function(d) { return applyLatLngToLayer({geometry: {coordinates: d}}).y });
 // -------------------------------------------------------------------

 var ptFeatures = g.selectAll("circle")
 .data(featuresdata)
 .enter()
 .append("circle")
 .attr("r", 3)
 .attr("class", "waypoints");

 var linePath = g.selectAll(".lineConnect")
 .data([featuresdata])
 .enter()
 .append("path")
 .attr("class", "lineConnect");

 // This will be our traveling circle it will
 // travel along our path
 var marker = g.append("circle")
 .attr("r", 10)
 .attr("id", "marker")
 .attr("class", "travelMarker");

 var originANDdestination = [featuresdata[0], featuresdata[17]];
 var begend = g.selectAll(".drinks")
 .data(originANDdestination)
 .enter()
 .append("circle", ".drinks")
 .attr("r", 5)
 .style("fill", "red")
 .style("opacity", "1");

 // I want names for my coffee and beer
 var text = g.selectAll("text")
 .data(originANDdestination)
 .enter()
 .append("text")
 .text(function(d) { return d.properties.name })
 .attr("class", "locnames")
 .attr("y", function(d) { return -10 });

 // when the user zooms in or out you need to reset
 // the view
 map.on("viewreset", reset);
 // this puts stuff on the map!
 reset();
 transition();

 // Reposition the SVG to cover the features.
 function reset() {
 var bounds = d3path.bounds(collection),
 topLeft = bounds[0],
 bottomRight = bounds[1];

 text.attr("transform", function(d) {
 return "translate(" + applyLatLngToLayer(d).x + "," + applyLatLngToLayer(d).y + ")";
 });

 // for the points we need to convert from latlong
 // to map units
 begend.attr("transform", function(d) {
 return "translate(" + applyLatLngToLayer(d).x + "," + applyLatLngToLayer(d).y + ")";
 });

 ptFeatures.attr("transform", function(d) {
 return "translate(" + applyLatLngToLayer(d).x + "," + applyLatLngToLayer(d).y + ")";
 });

 // again, not best practice, but I'm hard coding
 // the starting point
 marker.attr("transform", function() {
 var y = featuresdata[0].geometry.coordinates[1]
 var x = featuresdata[0].geometry.coordinates[0]
 return "translate(" + map.latLngToLayerPoint(new L.LatLng(y, x)).x + "," + map.latLngToLayerPoint(new L.LatLng(y, x)).y + ")";
 });

 // Setting the size and location of the overall SVG container
 svg.attr("width", bottomRight[0] - topLeft[0] + 120)
 .attr("height", bottomRight[1] - topLeft[1] + 120)
 .style("left", topLeft[0] - 50 + "px")
 .style("top", topLeft[1] - 50 + "px");

 // ---------------------- Fixed linePath d Attribute ----------------------
 linePath.attr("d", function(d) {
     var coords = d.map(function(feature) {
         return feature.geometry.coordinates;
     });
     return toLine(coords);
 });
 // -------------------------------------------------------------------

 g.attr("transform", "translate(" + (-topLeft[0] + 50) + "," + (-topLeft[1] + 50) + ")");
 };
 // end reset

 // ---------------------- Added tweenDash Function ----------------------
 function tweenDash() {
     var l = this.getTotalLength(),
         i = d3.interpolateString("0," + l, l + "," + l);
     return function(t) { return i(t); };
 }
 // -------------------------------------------------------------------

 function transition() {
 linePath.transition()
 .duration(7500)
 .attrTween("stroke-dasharray", tweenDash)
 .each("end", function() {
 d3.select(this).call(transition);// infinite loop
 });
 }
});
</script>
</body>
</html>

Key Notes

  • The added CSS makes the path, marker, and waypoints visible so you can easily see the animation in action.
  • The tweenDash function uses the path's total length to animate the stroke-dasharray property, creating the illusion of the path being drawn over time.
  • We now extract raw coordinates from your GeoJSON Features before passing them to the line generator, which ensures the path is correctly rendered on the map.

内容的提问来源于stack exchange,提问作者Itziar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:27:18