JordanBarber
JordanBarber

Reputation: 2101

How to add links from CSV file to SVG elements generated with D3?

I am in a bit of a bind and need some with help with linking my svg elements with URL's contained in an CSV file. I have a symbols map with over 100 symbols. The symbols are based on coordinates pulled from longitude and latitude in a CSV file which also contains the links that I would like each unique symbol to link to. I know there is an easy way to do this, pretty sure I'm overlooking the solution.

My CSV file is as follows:

        name,longitude,latitude,city,state,url

College of Charleston,803,342,Charleston,SC,http://sitename.com/colleges/college-of-charleston/

etc...

My symbols are generated using D3 and placed on top of my SVG map. I am also using D3 to wrap the symbols in anchor tags. I simply want these anchor tags to link to the appropriate url that correlates with the latitude and longitudes of that particular symbol.

  /* Start SVG */
    var width = 960,
        height = 640.4,
        positions = [],
        centered;

    var bodyNode = d3.select('#Map').node();
    var list = $('.school-list').each(function(i){});
    var svg = d3.select("#Map");
    var contain = d3.select("#map-contain");
    var circles = svg.append("svg:g")
                     .attr("id", "circles");
    var g = d3.selectAll("g");

        // var locationBySchools = {};

        d3.csv("http://sitename.com/wp-content/themes/vibe/homepage/schools.csv",function(schools){
            schools = schools.filter(function(schools){
            var location = [+schools.longitude, +schools.latitude];
            // locationBySchools[schools.loc] = location;
            positions.push(location);
            return true;
        });



            circles.selectAll("circles")
            .data(schools)
            .enter().append("svg:a")
            .attr("xlink:href", function(d) { })
            .append("svg:circle")
            .attr("cx", function(d,i) {return positions[i][0]})
            .attr("cy", function(d,i) {return positions[i][1]})
            .attr("r", function(d,i) {return 6})
            .attr("i", function(d,i) {return i})
            .attr("class", "symbol")

Really stuck with this one...Any ideas?

Upvotes: 2

Views: 1274

Answers (1)

jshanley
jshanley

Reputation: 9128

The short answer is that you should simply return the url property from your data when you are assigning the xlink:href attribute:

.attr("xlink:href", function(d) { return d.url; }) 

However, there are a couple other issues with the code you posted.

Issue 1. circles.selectAll('circles')

This starts with a the selection of your g element, and within it, selects all elements with the tag-name circles. The problem is that circles is not a valid svg tag. This just creates an empty selection, which is okay in this case because the selection is only being used to create new elements. But, it's a bad habit to make dummy selections like this, and it can be confusing to others trying to understand your code. Instead, you should decide on a class name to give to each of the new link elements, and use that class name to make your selection. For example, if you decide to give them a class of link you would want to do the following:

First create a selection for all of the elements with class="link":

circles.selectAll('.link')

This selection will initially be empty, but when you use .data() to bind your data to it, it will be given an enter selection which you can use to create the new elements. Then you can add the class of link to the newly created elements:

.data(schools).enter().append('svg:a')
  .attr('class', 'link')

Issue 2. .attr("i", function(d,i) {return i})

This one's pretty straightforward, there is no such attribute as i on svg elements. If you want to store arbitrary data on an element to be able to access it later, you can use a data attribute. In this case you might want to use something nice and semantic like data-index.

Issue 3. positions.push(location)

This is a big one. I would not recommend that you make a separate array to store the altered values from your dataset. You can use an accessor function in your d3.csv() function call, and clean up the incoming data that way. It will save you from having to maintain consistent data across two separate arrays. The accessor function will iterate over the dataset, taking as input the current datum, and should return an object representing the adjusted datum that will be used. This is a good spot to use your unary operator to coerce your latitude and longitude:

function accessor(d) {
  return {
    name: d.name,
    longitude: +d.longitude,
    latitude: +d.latitude,
    city: d.city,
    state: d.state,
    url: d.url
  };
}

There are two different ways to hook the accessor function into your d3.csv() call:

Method 1: Add a middle parameter to d3.csv() so that the parameters are (<url>, <accessor>, <callback>):

d3.csv('path/to/schools.csv', accessor, function(schools) {
  // ...etc...
});

Method 2: Use the .row() method of d3.csv()

d3.csv('path/to/schools.csv')
  .row(accessor)
  .get(function(schools) {
     // ...etc...
  });

Now when you want to access the latitude and longitude in your preferred format, you can get them right from the bound data, instead of from an outside source. This keeps everything clean and consistent.


Putting all of this together, you get the following:

d3.csv('http://sitename.com/wp-content/themes/vibe/homepage/schools.csv')
  // provide the accessor function
  .row(function accessor(d) {
    return {
      name: d.name,
      longitude: +d.longitude,
      latitude: +d.latitude,
      city: d.city,
      state: d.state,
      url: d.url
    };
   })
  // provide a callback function
  .get(function callback(schools) {
    circles.selectAll('.link')
      .data(schools)
      .enter().append('svg:a')
        .attr('class', 'link')
        // point the link to the url from the data
        .attr('xlink:href', function(d) { return d.url; })
          .append('svg:circle')
            .attr('class', 'symbol')
            // now we can just use longitude and latitude
            //  since we cleaned them up in the accessor fn
            .attr('cx', function(d) { return d.longitude; })
            .attr('cy', function(d) { return d.latitude; })
            // constants can be assigned directly
            .attr('r', 6)
            .attr('data-index', function(d,i) { return i; });
  });

Upvotes: 5

Related Questions