rob.m
rob.m

Reputation: 10571

How to check if a svg path has a class that matches a value in array and if so add a new class

I have an array and some svg path elements (I am using leaflet map). I need to check if the class of a path matches one of the values in my array and if so add a class fadeIn to it.

var foundNations = ["usa", "France", "Italy"];
document.querySelectorAll('path').forEach(path => {
  if (foundNations.includes(path.className)) {
   path.className.add('fadeIn');
    console.log(path.className);
  }
});
(function($) {
    var map = L.map('map').setView([45.4655171, 12.7700794], 2);
    map.fitWorld().zoomIn();
    L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
      attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' + '<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' + 'Imagery © <a href="http://mapbox.com">Mapbox</a>',
      id: 'mapbox.light'
    }).addTo(map);

    var mapHalfHeight = map.getSize().y / 2,
    container = map.zoomControl.getContainer(),
    containerHalfHeight = parseInt(container.offsetHeight / 2),
    containerTop = mapHalfHeight - containerHalfHeight + 'px';

    container.style.position = 'absolute';
    container.style.top = containerTop;
    map.scrollWheelZoom.disable();
    var southWest = L.latLng(-89.98155760646617, -180),
    northEast = L.latLng(89.99346179538875, 180);
    var bounds = L.latLngBounds(southWest, northEast);
    map.setMaxBounds(bounds);
    map.on('drag', function() {
      map.panInsideBounds(bounds, { animate: false });
    });

  // get color depending on population density value
    function getColor(d) {
      return d > 1000 ? '#800026' :
        d > 500  ? '#BD0026' :
        d > 200  ? '#E31A1C' :
        d > 100  ? '#FC4E2A' :
        d > 50   ? '#FD8D3C' :
        d > 20   ? '#FEB24C' :
        d > 10   ? '#FED976' :
              '#FFEDA0';
    }

    function style(feature) {
      return {
        weight: 1,
        opacity: 1,
        color: '#ffffff',
        dashArray: '',
        fillOpacity: 0,
        fillColor : '#FF0080',
        className: feature.properties.name
      };
    }

    var geojson;

    function selectNation(e) {

    }


   function onEachFeature(feature, layer) {
      layer.on({
        click: selectNation
      });
    }

    geojson = L.geoJson(statesData, {
      style: style,
      onEachFeature: onEachFeature
    }).addTo(map);
})( jQuery );
#map {
  width: 100vw;
  height: 100vh;
}

.fadeIn {
  fill: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
<link href="https://unpkg.com/[email protected]/dist/leaflet.css" rel="stylesheet"/>
<script src="https://www.jikupin.com/world.json"></script>

<div id='map'></div>

Upvotes: 0

Views: 820

Answers (1)

IvanSanchez
IvanSanchez

Reputation: 19069

Your bug is subtle. You are starting with an Array of Strings:

var nationList = ["usa", "france", "italy"];

Then you're calling String.prototype.includes, passing path.className as an argument.

if (foundNations.includes(path.className)) { path.classList.add('fadeIn')

In there, you're implicitly assuming that path.className is a String. But, surprise surprise, it's not a String, it's a SVGAnimatedString!

console.log(path.className)
> [object SVGAnimatedString] {
   animVal: "germany",
   baseVal: "germany"
  }

Yes, class names for SVG elements can be modified during animations in some edge cases.

What you probably want to do is use the baseVal property of the SVGAnimatedStrings:

console.log(typeof path.className.baseVal)
> "string"

And now everything should work more closely to the way you expect:

if (foundNations.includes(path.className.baseVal)) {
  path.classList.add('fadeIn')
}
console.log(path.className.baseVal);
> "spain fadeIn"


You have a second problem, due to another assumption. You're assuming that path.className contains just one class name, but according to the documentation, emphasis mine:

cName is a string variable representing the class or space-separated classes of the current element.

In fact, if you use the developer tools available in your browser to inspect the SVG elements, you'll see things like...

<path class="Italy leaflet-interactive" stroke="#ffffff" ....></path>

So in this case, you're assuming that the className.baseVal is going to be the string "Italy", but in reality, it takes the value "Italy leaflet-interactive".

The approach here is to use Element.classList to iterate through the class names to see if any of them match a given group.



Furthermore, I think that this is an instance of the XY problem. I don't think you wanted to ask

How to check if a SVG path has a class that maches foo?

but rather

How to symbolize a SVG polygon in Leaflet when the feature matches foo?

Because I think that it is way more elegant to move the checks into the style callback function, like:

geojson = L.geoJson(statesData, {
  style: function(feature){ 
    var polygonClassName = feature.properties.name;
    if (nationList.contains(feature.properties.name)) {
        polygonClassName += ' fadeIn';
    }
    return {
      weight: 1,
      opacity: 1,
      color: '#ffffff',
      dashArray: '',
      fillOpacity: 0,
      fillColor : '#FF0080',
      className: polygonClassName
    };
  },
  onEachFeature: onEachFeature
}).addTo(map);

Leaflet offers convenient functionality like L.Path.setStyle that hides the complexity of dealing with selectors and SVG classes directly, as long as you keep references to your instances of L.Polygon around (which, in this case, you can do in the onEachFeature callback).

Upvotes: 4

Related Questions