chmosta
chmosta

Reputation: 33

Adding Dots to d3 SVG Map

I am new to d3 and am having some trouble adding dots to my SVG map. I am loading in the SVG map and then attempting to append the coordinates from my data, which is an array of objects formatted like this:

{schoolName: "Harvard University", gpa: 4, state: "MA", city: "Cambridge", longitude: -71.10973349999999, latitude: 42.3736158}

The result of my code below is that I am appending a set of circles below the SVG tag, but none are appearing on the map and I am receiving what looks like a syntax error that I do not see in my code.

Translate Error: Translate Error

What am I doing incorrectly here?

var coords = require('./data/coordinates')

document.addEventListener("DOMContentLoaded", () => {

    // loads svg map
    d3.xml('src/data/usa2.svg', function(data){
        document.body.append(data.documentElement)
    })

    // adjusts data to include coordinates for each city
    cities = cities.default
    let schoolData = []
    d3.json("src/data/schoolInfo.json", function(schools){
        schools.map(el => {
            let currentCity = cities.filter(function(cities){return cities.city === el.city})
            el["hs-gpa-avg"] !== null && currentCity.length > 0 ? schoolData.push({"schoolName": el["displayName"], "gpa": el["hs-gpa-avg"], "state": el["state"], "city": el["city"], "longitude": currentCity[0]["longitude"], "latitude": currentCity[0]["latitude"]}) : ''
        })


    var width = 1200,
        height = 720;

    var albersProjection = d3.geoAlbers()
        .scale(1000)
        .rotate([70, 0])
        .center([-15, 35])
        .translate([width/2, height/2]);

    var svg = d3.select('svg')

    svg.selectAll("circle")
        .data(schoolData).enter()
        .append("circle")
        .attr("transform", function(d) {
            let coords = albersProjection([ d.longitude, d.latitude ]);
            return `translate(${coords[0]}px, ${coords[1]}px)`
        })
        .attr("r", "8px")
        .attr("fill", "red")   
    })

})

In addressing the error related to the translation, I still have circles misplaced, as predicted in this answer:

Misplaced points: Misaligned points to coordinates on map

Upvotes: 3

Views: 2553

Answers (1)

Andrew Reid
Andrew Reid

Reputation: 38151

SVG transform attributes expect numbers, plain numbers without units. You can, however, specify units if you use CSS (style="transform: ..." as opposed to specifying the transform attribute: transform = "...").

The below snippet compares setting units with a CSS style property, setting the transform attribute directly without units, and setting the transform attribute directly with units. It should cause the same error on the last rectangle (which is not positioned correctly due to the error, it should be below the blue rectangle):

<svg>
   <rect style="transform:translate(200px,0px)" width="20" height="20" fill="steelblue"></rect>
   <rect transform="translate(100,0)" width="20" height="20" fill="crimson"></rect>   
   <rect transform="translate(200px,50px)" width="20" height="20" fill="orange"></rect>
</svg>

My error, on the last rectangle: "Error: <rect> attribute transform: Expected ')', "translate(200px,50px)"."

This is the same as you, the solution would be to simply remove the px from your translation values. You can also specify a CSS property by using .style() as opposed to .attr().


As for your next question: why are the circles still not placed right, but now you have no errors, well this is a different problem. This is a common problem.

You have an SVG version of the US - this SVG comprises of vertices on a 2d plane: projected points with their units in pixels. As the SVG is already projected, your SVG features are pre-projected.

Your geographic data comprises of 3D points (points on a sphere) with their units in degrees. Without knowing the projection used to create the SVG, you can't align geographic point and preprojected point.

This is a common issue with pre-projected topojson/geojson (also in which the coordinates are in a 2D space with the units in pixels). The solutions are the same:

  • attempt to replicate the projection used in creating the SVG when projecting geographic points on top of it (difficult)

  • use only unprojected coordinates for both layers when drawing the map (no premade SVGs, unless you made them). (easy)

  • use only preprojected coordinates for both layers when drawing the map (but to make this for the points, you still need to know the projection used to create the SVG). (not worth it)

These questions and answers address the analogous problem, just with preprojected topojson/geojson rather than SVGs (it is fair to compare as a geoPath produces SVG vectors usually):

The second one includes a link to an unprojected topojson of the US:

https://bl.ocks.org/mbostock/raw/4090846/us.json

We can use this with our unprojected points - we simply use the same projection for point and area.

We can use this with topojson.js to produce a geojson array:

 var geojson = topojson.feature(data,data.objects.states).features;

And once we have our projection defined, can use projection([long,lat]) to project points, and d3.geoPath(projection) to project geojson.

Where data is the parsed JSON - the value passed to the d3.json callback function.

Upvotes: 1

Related Questions