Search code examples
javascriptd3.js

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


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

I only use a section of the USA/CAN map to create an overview of trips. This results in the problem that the states are not always displayed in full based on a bbox with max trip coordinates. How can I display the abbreviations for each state, which are also partially available? On one hand, so that all states have an abbreviation and, on the other hand, so that the TripRoute does not cover these abbreviations.

 // --- 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");
      // --- thx to Frenchy for his solution --------------
      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);

    // --- 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!

@Frenchy : The abbreviations can be found 2 times:

  • in the 'const tbdatResult --> states', where the array-id = btnId.
  • in 'USA_CAN.geojson --> let baseMap'. But here I don't know how to get only a subset of the abbreviations from the total set (USA_CAN.geojson = let baseMap) and how to place them in the map. Namely only the abbreviations of the visited states, even if they are only partially displayed in the map. I have set the remarks in the fiddle under the comment 'append clipPath' and the code I am looking for should be implemented around here.

Solution

  • You could apply text by using this method:

    // --- abbr name for states within clipPath ????
    // --- use "properties":{"name":"Washington","id":"US-WA" ....
    // --- last 2 characters from the id, in this example 'WA'
    
         svg.selectAll("text")
        .data(basem)
        .enter()
        .append("text")
        .attr("fill", "black")
        .attr("transform", function (d) { 
            //check if text appears and modify cood
            var centroid = path.centroid(d);
            if (centroid[1] < 10) centroid[1] = 10;
            if (centroid[1] > height - 15) centroid[1] = height - 15;
            if (centroid[0] > width - 15) centroid[0] = width - 15;
            if (centroid[0] < 10) centroid[0] = 15;
            return "translate(" + centroid[0] + "," + centroid[1] + ")"
        })
        .attr("text-anchor", "middle")
        .attr("dy", ".35em")
        .text(function(d) {
              return d.id.slice(-2);
        });
    

    // --- aos -------------------------------------------------
    window.addEventListener('load', () => {
      AOS.init({
        duration: 1000,
        easing: "ease-in-out",
        once: true,
        mirror: false
      });
    });
    // --- common func : add object ----------------------------
    var addToObject = function (obj, key, value, index) {
      var temp = {};
      var i = 0;
      for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) {
          if (i === index && key && value) {
            temp[key] = value;
          }
          temp[prop] = obj[prop];
          i++;
         }
      }
      if (!index && key && value) {
        temp[key] = value;
      }
      return temp;
    };
    
    // --- hardcoded const for tests only - no PHP/mySQL part required ------------
    const tbdatResult = [{"id":"000001","mons":"05","year":"1979","miles":6112,"states":"AZ, CA, CO, NV, NM, TX, UT","dest":"LAX","activ":0},{"id":"000002","mons":"09","year":"1986","miles":10617,"states":"AZ, CA, CO, ID, NV, NM, OR, SD, UT, WY","dest":"LAX","activ":0},{"id":"000003","mons":"09","year":"1988","miles":11084,"states":"AZ, CA, CO, HI, MT, NV, NM, OR, SD, UT, WY","dest":"LAX","activ":0},{"id":"000004","mons":"09","year":"1990","miles":10840,"states":"AZ, CA, ID, IL, KS, KY, MO, MT, NE, NV, NM, OR, TN, TX, UT, WA, WY","dest":"LAX","activ":0},{"id":"000005","mons":"09","year":"1991","miles":4358,"states":"AZ, CA, NV, UT","dest":"LAX","activ":0},{"id":"000006","mons":"09","year":"1992","miles":4925,"states":"AZ, CA, NV, UT","dest":"LAX","activ":0},{"id":"000007","mons":"09","year":"1993","miles":6581,"states":"AZ, CA, NV, NM, OR, UT","dest":"LAX","activ":0},{"id":"000008","mons":"09","year":"1994","miles":4197,"states":"AZ, CA, CO, NV, UT","dest":"LAX","activ":0},{"id":"000009","mons":"09","year":"1995","miles":6582,"states":"AZ, CA, NV, NM, UT","dest":"LAX","activ":0},{"id":"000011","mons":"09","year":"1996","miles":6494,"states":"AZ, CA, ID, MT, NV, OR, UT, WA, WY","dest":"LAX","activ":0},{"id":"000012","mons":"09","year":"1997","miles":5816,"states":"AZ, CA, CO, ID, NV, NM, UT, WY","dest":"LAX","activ":0},{"id":"000013","mons":"09","year":"1998","miles":6787,"states":"CA, ID, MT, NV, OR, WA, AB, BC","dest":"LAX","activ":0},{"id":"000014","mons":"09","year":"1999","miles":5903,"states":"AZ, CA, ID, MT, NV, OR, UT, WA, WY","dest":"LAX","activ":0},{"id":"000015","mons":"09","year":"2000","miles":5600,"states":"AZ, CA, NV, UT","dest":"LAX","activ":0},{"id":"000017","mons":"05","year":"2002","miles":5363,"states":"AZ, CA, NV, UT","dest":"LAX","activ":0},{"id":"000018","mons":"09","year":"2002","miles":7659,"states":"AZ, CA, ID, MT, NV, OR, UT, WA, WY, AB","dest":"LAX","activ":0},{"id":"000019","mons":"09","year":"2003","miles":8362,"states":"AZ, CA, ID, MT, NV, OR, UT, WA, WY","dest":"LAX","activ":0},{"id":"000020","mons":"09","year":"2004","miles":5481,"states":"AZ, CA, CO, NV, UT","dest":"LAX","activ":0},{"id":"000021","mons":"09","year":"2005","miles":7273,"states":"AZ, CA, CO, ID, MT, NV, OR, UT, WA, WY","dest":"LAX","activ":0},{"id":"000022","mons":"09","year":"2006","miles":6980,"states":"AZ, CA, CO, ID, MT, NV, UT, WY","dest":"LAX","activ":0},{"id":"000023","mons":"09","year":"2007","miles":6781,"states":"AZ, CA, ID, NV, OR, UT, WY","dest":"LAX","activ":0},{"id":"000024","mons":"09","year":"2008","miles":7187,"states":"AZ, CA, ID, MT, NV, OR, UT","dest":"LAX","activ":0},{"id":"000025","mons":"09","year":"2009","miles":5126,"states":"AZ, CA, CO, NV, UT","dest":"LAX","activ":0},{"id":"000026","mons":"09","year":"2010","miles":6658,"states":"AZ, CA, CO, ID, NV, OR, UT, WY","dest":"LAX","activ":0},{"id":"000027","mons":"09","year":"2011","miles":6241,"states":"AZ, CA, CO, NV, NM, UT","dest":"LAX","activ":0},{"id":"000028","mons":"09","year":"2012","miles":5032,"states":"AZ, CA, CO, NV, NM, UT","dest":"LAX","activ":0},{"id":"000029","mons":"09","year":"2013","miles":6421,"states":"AZ, CA, CO, NV, NM, UT, WY","dest":"LAX","activ":0},{"id":"000030","mons":"09","year":"2014","miles":7247,"states":"AZ, CA, CO, ID, MT, NV, NM, OR, UT, WA, WY","dest":"LAX","activ":0},{"id":"000031","mons":"09","year":"2015","miles":7105,"states":"CA, NV, OR, WA","dest":"LAX","activ":0},{"id":"000032","mons":"09","year":"2016","miles":6240,"states":"CA, CO, ID, NV, OR, UT, WY","dest":"LAX","activ":0},{"id":"000033","mons":"09","year":"2017","miles":7963,"states":"AZ, CA, CO, NE, NV, NM, SD, UT, WY","dest":"LAX","activ":1},{"id":"000034","mons":"10","year":"2018","miles":7359,"states":"AZ, CA, CO, NV, NM, UT","dest":"LAX","activ":1},{"id":"000035","mons":"10","year":"2019","miles":6519,"states":"AZ, CA, NV, NM, UT","dest":"LAX","activ":1},{"id":"000036","mons":"05","year":"2022","miles":4396,"states":"AZ, CA, CO, NV, NM, UT","dest":"LAS","activ":1},{"id":"000037","mons":"10","year":"2022","miles":6826,"states":"AZ, CA, ID, NV, OR, UT, WA","dest":"LAS","activ":1},{"id":"000038","mons":"09","year":"2023","miles":9472,"states":"AZ, CA, CO, ID, MT, NV, OR, MT, UT, WA","dest":"LAS","activ":0}];
    
    // --- tooltips ----------------------------------------------------------------
    document.querySelectorAll('button[data-bs-title]').forEach(tooltipTriggerElem => new bootstrap.Tooltip(tooltipTriggerElem))
    
    // --- get btnId ---------------------------------------------------------------
    document.querySelector("#trnMap").addEventListener("show.bs.modal", (e) => {
      let target = e.target;
      let btnId = e.relatedTarget;
      let selectedPeriod = tbdatResult.find((period) => period.id === btnId.id);
      let currentLink = `?year=${selectedPeriod.year}&mons=${selectedPeriod.mons}`;
      let currentTopo = `${selectedPeriod.year}_${selectedPeriod.mons}`;
      
      // --- airport coords for destination ----------------------------------------
      let destArray = [];
      if (selectedPeriod.dest === "LAX") {
        destArray = ["-118.4089", "33.9434"];
      } else if (selectedPeriod.dest === "LAS") {
        destArray = ["-115.1371", "36.0862"];
      }
        
      // --- modal - header ------------------------------------
      target.querySelector(".modal-header .tour").innerHTML = `<h4 class="modal-title">Overview ${selectedPeriod.mons}_${selectedPeriod.year}</h4><br \>${selectedPeriod.states}`;
      
      // --- modal - footer ------------------------------------
      let modalSelect = document.getElementById("modal-select");
      // *** not required for the demo part - close button only ********************
      // if (selectedPeriod.activ == "1") {
      //   modalSelect.innerHTML = `<a href="trn_list.php${currentLink}" class="btn btn_sm btn-lgw">Tabelle</a><a href="trn_map.php${currentLink}" class="btn btn_sm btn-lgw">Karte</a><button type="button" class="btn btn_sm btn-lgw" data-bs-dismiss="modal">Close</button>`;
      // } else {
      //   modalSelect.innerHTML = `<a href="trn_list.php${currentLink}" class="btn btn_sm btn-lgw">Tabelle</a><button type="button" class="btn btn_sm btn-lgw" data-bs-dismiss="modal">Close</button>`;
      // }
      modalSelect.innerHTML = `<button type="button" class="btn btn_sm btn-lgw" data-bs-dismiss="modal">Close</button>`;
      
      // --- 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 bbox for all states
              let bbox = baseMap.features.map(t => {
                  let p = {};
                  p["id"] = t.id;
                  p["box"] = d3.geoBounds(t);
                  return p;
              });
    
              let res = bbox.filter(t => {
                  if ((t.box[1][0] <= tourMapBbox[1][0] && t.box[1][0] >= tourMapBbox[0][0] &&
                       t.box[0][1] >= tourMapBbox[0][1] && t.box[0][1] <= tourMapBbox[1][1]) ||
                      (t.box[0][0] <= tourMapBbox[1][0] && t.box[0][0] >= tourMapBbox[0][0] &&
                       t.box[0][1] >= tourMapBbox[0][1] && t.box[0][1] <= tourMapBbox[1][1]) ||
                      (t.box[0][0] <= tourMapBbox[1][0] && t.box[0][0] >= tourMapBbox[0][0] &&
                       t.box[1][1] >= tourMapBbox[0][1] && t.box[1][1] <= tourMapBbox[1][1]) ||
                      (t.box[1][0] <= tourMapBbox[1][0] && t.box[1][0] >= tourMapBbox[0][0] &&
                       t.box[1][1] >= tourMapBbox[0][1] && t.box[1][1] <= tourMapBbox[1][1])) {
                      return true;
                  }
                  return false;
              }).map(t => t.id);
              //get only states that fits with Tour
              let basem = baseMap.features.filter(t => res.includes(t.id));
     
    
        // --- 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.selectAll("text")
        .data(basem)
        .enter()
        .append("text")
        .attr("fill", "black")
        .attr("transform", function (d) { 
            //check if text appears and modify cood
            var centroid = path.centroid(d);
            if (centroid[1] < 10) centroid[1] = 10;
            if (centroid[1] > height - 15) centroid[1] = height - 15;
            if (centroid[0] > width - 15) centroid[0] = width - 15;
            if (centroid[0] < 10) centroid[0] = 15;
            return "translate(" + centroid[0] + "," + centroid[1] + ")"
        })
        .attr("text-anchor", "middle")
        .attr("dy", ".35em")
        .text(function(d) {
              return d.id.slice(-2);
        });
        
    
        // --- 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 });
    });
    <html>
    <head>
      <meta charset="utf-8" />
      <meta content="width=device-width, initial-scale=1.0, minimum-scale=1.0" name="viewport">
      <title>tour overview by years</title>
      <!-- === Vendor CSS Files ================================ -->
      <link href="https://cdnjs.cloudflare.com/ajax/libs/aos/2.3.4/aos.css" rel="stylesheet">
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
      <link href="https://cdn.jsdelivr.net/npm/swiper@9/swiper-bundle.min.css" rel="stylesheet">
    
      <!-- +++++++++++++++++++++++++++++++++++++++++++ my style - start ++++++++++++++++++++++++++++++++ -->
      <style>
    /*--------------------------------------------------------------
    ---  general: body, h.., a, p, q
    --------------------------------------------------------------*/
    body {
      font-family: "Open Sans", sans-serif;
      color: #333333;
    }
    /*--------------------------------------------------------------
    --- general: sections
    --------------------------------------------------------------*/
    section {
      padding: 0 0;
      overflow: hidden;
    }
    .section-bg {
      background-color: #f1f1f1;
    }
    .section-title {
      color: #006eff;
      padding: 40px 0 20px 0;
    }
    
    .section-title h2 {
      font-size: 1.0rem;
      font-weight: 500;
      margin: 0 0 5px 0;
      letter-spacing: 2px;
      text-transform: uppercase;
      color: #006eff;
      font-family: "Poppins", sans-serif;
    }
    .section-title h2::after {
      content: "";
      width: 120px;
      height: 1px;
      display: inline-block;
      background: #006eff;
      margin: 4px 10px;
    }
    
    .section-title p {
      margin: 0;
      font-size: 28px;
      font-weight: 300;
      font-family: "Poppins", sans-serif;
      color: #333333;
    }
    .section-title-sub {
      margin-bottom: 0.3rem;
      text-align: justify;
    }
    .section-hr {
      content: "";
      position: unset;
      left: 0;
      bottom: 0;
      height: 1px;
      background: #bfbfbf;
      margin-bottom: 40px;
    }
    .section-sub-hr {
      content: "";
      position: unset;
      left: 0;
      bottom: 0;
      height: 1px;
      background: #bfbfbf;
      margin-bottom: 20px;
    }
    .section-sub-hr-10 {
      content: "";
      position: unset;
      left: 0;
      bottom: 0;
      height: 1px;
      background: #bfbfbf;
      margin-bottom: 10px;
    }
    @media (max-width: 1200px) {
      .section-title {
      padding: 20px 0 10px 0; 
      }
      .section-title p {
        font-size: 20px;
      }
      .section-hr {
        margin-bottom: 20px;
      }
    }
    /* -------------------------------------------------------------------
    --- touren: customer style
    --------------------------------------------------------------------*/
    .touren {
      padding: 20px 0;
    }
    .touren-box {
      padding: 10px;
    }
    .touren .btn {
      margin-bottom: 0.5rem;
      width: 100% !important;
      padding: 6px 0;
    }
    @media (min-width: 768px) {
      .touren .btn {
        width: calc(50% - 8px) !important;
        margin: 4px;
        padding: 8px 0;
      }
    }
    @media (min-width: 1200px) {
      .touren .btn {
        width: calc(16.66% - 8px) !important;
        margin: 4px;
        padding: 10px 0;
      }
    }
    .touren a {
      color: white;
    }
    .trn-tooltip {
      --bs-tooltip-bg: var(--bs-primary);
    }
    /*--------------------------------------------------------------
    ---  general: customer blue frame with shadow
    --------------------------------------------------------------*/
    .rounded-frame {
      position: relative;
      display: flex;
      flex-direction: column;
      min-width: 0;
      box-shadow: 0px 2px 12px rgba(0, 0, 0, 0.08);
      transition: 0.3s;
      overflow: hidden;
      border-radius: 5px;
      margin-bottom: 10px;
      color: #333333;
      border: 1px solid #0063e6;
    }
    .grow-frame:hover {
      transform:scale(1.05);
    }
    /*--------------------------------------------------------------
    --- button: customer style
    --------------------------------------------------------------*/
    [type="button"]:not(:disabled),
    [type="reset"]:not(:disabled),
    [type="submit"]:not(:disabled),
    button:not(:disabled) {
      box-shadow: none;
      outline: 0;
    }
    .btn-lgw {
      font-size: .9rem;
      color: var(--bs-white);
      background: #338bff;
      border: 1px solid #333333;
      transition: 0.4s;
      border-radius: 0.3rem;
    }
    .btn-lgw:hover {
      color: #0058cc;
      background-color: #e6e6e6;
      border: 1px solid #0063e6;
    }
    .btn:focus,
    .btn-lgw:focus {
      outline: 0;
      box-shadow: none;
    }
    /* -------------------------------------------------------------------
    --- modal: customer style
    --------------------------------------------------------------------*/
    .modal-header-lines {
      display: block;
      line-height: 0.5;
    }
    .modal-map-bg {
      background: #f1f1f1;
    }
    /* -------------------------------------------------------------------
    --- tooltip: customer style
    --------------------------------------------------------------------*/
    .custom-tooltip {
      --bs-tooltip-bg: #666666;
    }
        
      </style>
    
    </head>
      
    <body>
      <!-- === main ============================================ -->
      <main id="main" class="section-bg">
        <!-- div id="getSize"></div -->
        <!-- ======= section(s) ======= -->
        <section id="touren" class="section-bg">
          <div class="container" data-aos="fade-up">
             <div class="section-title">
              <h2>On The Road</h2>
              <p>overview</p>
            </div>
            <div class="section-title-sub">testdata USA + CAN : 05_1979, others (05_2022, 10_2022, 09_2023) real data</div>
            <div class="section-hr"></div>
            <div> <!--  class="aos-padding" data-aos="fade-up" data-aos-delay="100" -->
              <div class="touren" id="touren">
                <div class="touren-box rounded-frame">
                 <div class="d-flex flex-wrap">
                    <button type="button" id="000001" class="btn btn-sm btn-lgw" data-bs-html="true" data-bs-trigger="hover" data-bs-title = "<b>Tour 05_1979</b><br \>Ausgangspunkt: LAS</b><br \>States: AZ, CA, CO, NV, NM, TX, UT</b><br \>Distanz: 6.112 mi  ≈  9.836 km" data-bs-toggle="modal" data-bs-target="#trnMap">05_1979</button>
                    <button type="button" id="000036" class="btn btn-sm btn-lgw" data-bs-html="true" data-bs-trigger="hover" data-bs-title = "<b>Tour 05_2022</b><br \>Ausgangspunkt: LAS</b><br \>States: AZ, CA, CO, NV, NM, UT</b><br \>Distanz: 4.396 mi  ≈  7.075 km" data-bs-toggle="modal" data-bs-target="#trnMap">05_2022</button>
                    <button type="button" id="000037" class="btn btn-sm btn-lgw" data-bs-html="true" data-bs-trigger="hover" data-bs-title = "<b>Tour 10_2022</b><br \>Ausgangspunkt: LAS</b><br \>States: AZ, CA, ID, NV, OR, UT, WA</b><br \>Distanz: 6.826 mi  ≈  10.985 km" data-bs-toggle="modal" data-bs-target="#trnMap">10_2022</button>
                    <button type="button" id="000038" class="btn btn-sm btn-lgw" data-bs-html="true" data-bs-trigger="hover" data-bs-title = "<b>Tour 09_2023</b><br \>Ausgangspunkt: LAS</b><br \>States: AZ, CA, CO, ID, MT, NV, NM, OR, UT, WA</b><br \>Distanz: 9.472 mi  ≈  15.244 km" data-bs-toggle="modal" data-bs-target="#trnMap">09_2023</button>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </section>
      </main>
      
      <!-- === modal: map tour ================================== -->
      <div id="trnMap" class="modal fade" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
        <div class="modal-dialog modal-lg" id="trnDialog">
          <div class="modal-content">
            <div class="modal-header modal-header-lines" id="modal-title">
              <div class="tour"></div>
            </div>
            <div class="modal-body d-flex justify-content-center modal-map-bg">
              <div id="tourMap" class="chart--container tour"></div>
            </div>
            <div class="modal-footer tour" id="modal-select">
            </div>
          </div>
        </div>
      </div>
      <!-- === modal part: end ================================= -->
      
      <!-- === Vendor JS Files ================================= -->
      <script src="https://cdnjs.cloudflare.com/ajax/libs/aos/2.3.4/aos.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/js/bootstrap.bundle.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/swiper@9/swiper-bundle.min.js"></script>
      <!-- === zingchart related ================================ -->
      <script src="https://d3js.org/d3.v7.min.js"></script>
      <script src="https://unpkg.com/topojson@3"></script>
      <script>console.log('... d3 version:', d3.version); </script>
    
    </body>
    </html>