Search code examples
javascriptd3.js

How to display the abbreviation of a US-state/CAN-province in a map section - part 1?


Original question splitted in 2 parts --> at the suggestion of Frenchy

1) How can I display a 2-digit abbreviation for all touched states within a clipPath, not hidden by the route line?

  1. How can I display a full circle if the coordinate point (.append("circle")) which is located directly at MapBBox border? Currently, only a semicircle is displayed (which is actually correct) - see complete example, button 05_1979
  // --- clear svg -----------------------------------------
  d3.select("svg").remove();
  
  // --- get modal size --------------------------------------------------------
  document.querySelector(".modal").addEventListener("shown.bs.modal", function (e) {
    let modalWidth = document.querySelector(".modal-dialog").clientWidth;
    let modalHeight = document.querySelector(".modal-dialog").clientHeight;
  
    // --- load both json files, V7 method -----------------
    Promise.all([
      d3.json("https://gist.githubusercontent.com/manu-75/06b031548def239d7038e7157a3c204c/raw/8da47d28b846b6b232cad57692d737e05ddebee3/USA_CAN.geojson"),
      d3.json(`https://gist.githubusercontent.com/manu-75/06b031548def239d7038e7157a3c204c/raw/8da47d28b846b6b232cad57692d737e05ddebee3/${currentTopo}.topojson`),
      ]).then(function (data) {
      let baseMap = data[0];
      let tourMap = data[1];
      let geojsonTour = topojson.feature(tourMap, tourMap.objects[currentTopo]);
      
    // --- get bbox of tourMap -----------------------------
    let tourMapBbox = d3.geoBounds(geojsonTour);
    
    // --- get aspectRatio from topojson -------------------
    let widthBbox = tourMapBbox[1][0] - tourMapBbox[0][0];
    let heightBbox = tourMapBbox[1][1] - tourMapBbox[0][1];
    let aspectRatio = widthBbox / heightBbox;
    let tourHeightCalc = Math.round(modalWidth / aspectRatio);
    
    // --- margins, width, height --------------------------
    let margin = { top: 20, right: 20, bottom: 20, left: 20 },
      width = modalWidth - margin.left - margin.right,
      height = tourHeightCalc - margin.top - margin.bottom;
    
    // --- svg -------------------------------------------
    let svg = d3
      .select("#tourMap")
      .append("svg")
      .attr("width", width)
      .attr("height", height);

    // --- projection ------------------------------------
    let projection = d3.geoMercator().fitSize([width, height], geojsonTour);

    // --- path ------------------------------------------
    let path = d3.geoPath().projection(projection);

    // --- svg append tour -------------------------------
    svg
      .append("path")
      .datum(geojsonTour)
      .attr("d", path)
      .attr("fill", "none")
      .attr("stroke", "#ff674d")
      .attr("stroke-width", 1.5);

    // --- svg append origin circle ----------------------
    svg
      .append("circle")
      .attr("cx", projection(destArray)[0])
      .attr("cy", projection(destArray)[1])
      .attr("r", 4)
      .attr("fill", "#338bff");

    // --- clipPath defined by tourMapBbox ----------------
    const clipPath = svg
      .append("clipPath")
      .attr("id", "clip")
      .append("rect")
      .attr("x", projection(tourMapBbox[0])[0])
      .attr("y", projection(tourMapBbox[1])[1])
      .attr("width", projection(tourMapBbox[1])[0] - projection(tourMapBbox[0])[0])
      .attr("height", projection(tourMapBbox[0])[1] - projection(tourMapBbox[1])[1]);

    // --- append clipPath -------------------------------
    const g = svg.append("g").attr("clip-path", "url(#clip)");
    
    // --- abbr name for states within clipPath ????
    // --- use "properties":{"name":"Washington","id":"US-WA" ....
    // --- last 2 characters from the id, in this example 'WA'

    // --- svg append baseMap ----------------------------
    svg
      .append("path")
      .datum(baseMap)
      .attr("d", path)
      .attr("fill", "transparent")
      .attr("stroke", "black")
      .attr("stroke-opacity", 0.5)
      .attr("stroke-width", 0.3);

    });
  }, { once: true });

here's my full jsfiddle example

Any help appreciated!


Solution

  • to have a complete circle, you have to check if the circle is outside of the svg dimension:

    for the first button, the circle is outside the svg , so you adapt the height like this:

    // --- svg append origin circle ----------------------
    svg
      .append("circle")
      .attr("cx", projection(destArray)[0])
      .attr("cy", projection(destArray)[1])
      .attr("r", 4)
      .attr("fill", "#338bff");
            
      const r = parseInt(svg.select("circle").node().getAttribute("r"));
      const cy = parseInt(projection(destArray)[1]) + 1; 
      const tcy = r + cy;
      if (tcy > height)
        svg.node().setAttribute("height",tcy);
    

    if needed, you could do the same operation with the width....

    you could check the fiddle