原始问题分为两部分 --> 根据 Frenchy 的建议
我仅使用美国/加拿大地图的一部分来创建旅行概览。这导致了状态并不总是基于具有最大行程坐标的bbox完整显示的问题。如何显示每个州的缩写(部分可用)?一方面,让所有州都有缩写,另一方面,让 TripRoute 不涵盖这些缩写。
// --- 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 });
任何帮助表示赞赏!
@法国人: 缩写可以找到2次:
您可以使用此方法应用文本:
// --- 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(baseMap.features)
.enter()
.append("text")
.attr("fill", "black")
.attr("transform", function(d) {
var centroid = path.centroid(d);
return "translate(" + centroid[0] + "," + centroid[1] + ")"
})
.attr("text-anchor", "middle")
.attr("dy", ".35em")
.text(function(d) {
return d.id.split('-')[1];
});
// --- 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 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(baseMap.features)
.enter()
.append("text")
.attr("fill", "black")
.attr("transform", function(d) {
var centroid = path.centroid(d);
return "translate(" + centroid[0] + "," + centroid[1] + ")"
})
.attr("text-anchor", "middle")
.attr("dy", ".35em")
.text(function(d) {
return d.id.split('-')[1];
});
// --- 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>