3

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

Andrew Reid
  • 37,021
  • 7
  • 64
  • 83
chmosta
  • 33
  • 6
  • I've adjusted the question to keep the initial problem - otherwise the first part of my answer is no longer relevant and the question will have changed considerably in intent. The question should be capturing both issues that were originally present now. If you feel it doesn't capture both, feel free to change my edits. – Andrew Reid Oct 17 '19 at 05:11

1 Answers1

1

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.

Andrew Reid
  • 37,021
  • 7
  • 64
  • 83
  • Thank you! Removing the 'px' did get rid of my error. I'll look deeper into those links you've provided. It does seem like each use either geoJSON or TopoJSON, whereas I am just using straight JSON. Will that cause me issues? – chmosta Oct 17 '19 at 04:13
  • If you are using a geoPath, as you are for some features (`.attr("d", d3.geoPath().projection(projection))`), you are supplying it geojson as this is the only things that a geoPath will render. Though this could be a misread (and likely is, as it appears you are not in fact supplying valid geojson to the geoPath): if you are supplying non-valid geojson to a geoPath no error is thrown and nothing is rendered - in which case you have not shared how you are drawing the US states background seen in the image. This would be useful to see if it is not included. – Andrew Reid Oct 17 '19 at 04:20
  • I see - so it sounds like I may need to convert my json file into geojson to ensure geopath is supplied valid data. In regards to drawing the states background, it is located on the 4th line in my code above; d3 loads my local SVG file and appends it onto the DOM's body via the event listener. – chmosta Oct 17 '19 at 04:28
  • You can use JSON to draw the circles, no problem. You are already drawing them and positioning them with the projection already. In my quick read of the question, I assumed the geoPath in the code block was used to draw the US states (which appear in the screenshot) - but the geoPath in the question's code is not drawing anything because it is not being supplied valid geojson. No need to convert to geojson just to draw points you are already drawing when appending circles with JSON. If the US states and the points are not aligning, you should share the part of the code were you draw the US. – Andrew Reid Oct 17 '19 at 04:32
  • In the screenshot - I'm assuming, the `g` that is between paths and circles should contain paths without any `d` attribute data: they should be empty as they aren't supplied valid geojson (based on the code in the question). The removal of the code appending these paths - shown in the question - should have no consequence (`svg.append("g").selectAll("path").data(schoolData)...`) – Andrew Reid Oct 17 '19 at 04:35
  • Ahh I see thank you! I've updated my code snippet, which is now reflecting red dots on my map but they are not aligned to their proper coordinates. You're correct I do not need that line of code - my guess is that the misalignment may have to do with my albersProjection? – chmosta Oct 17 '19 at 04:51
  • Sorry for the confusion earlier. I totally missed that you were appending a straight SVG as opposed to creating the paths from a geojson - though the problem is the same: you are trying to match an unknown projection. It is much easier to ensure all the data uses one coordinate system (preprojected or unprojected) so there's no need to align multiple coordinate systems in the browser. To match the SVG's projection you need to know what parameters to use for a D3 Albers (parallels, center, rotation, scale, and translate) - it's possible that you could deduce this, but more info is needed. – Andrew Reid Oct 17 '19 at 05:03
  • No worries at all - thank you so much! I had previously spent so many hours trying to understand this and you explained this extremely well. Much appreciated!! – chmosta Oct 17 '19 at 13:40