chart = {
const color = d3.scaleQuantize([1, 10], d3.schemeBlues[9]);
const format = d => `${d}%`;
const path = d3.geoPath()
const width = 975
const height = 610
const counties = topojson.feature(us, us.objects.counties);
const states = topojson.feature(us, us.objects.states);
const statemap = new Map(states.features.map(d => [d.id, d]));
const statemesh = topojson.mesh(us, us.objects.states, (a, b) => a !== b);
const projection = d3.geoAlbersUsa().scale(1300).translate([487.5, 305])
const longBeachCoords = projection([-118.1937, 33.7701]);
let zl;
let isExploded = false;
let explodedStateId = null;
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");
const g = svg.append("g");
const legend = svg.append("text")
.attr("class", "legend")
.attr("x", 805)
.attr("y", 590)
.style("font-size", "12px")
.text("States: 0, Counties: 0");
g.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.join("path")
.attr("fill", 'pink')
.attr("d", path)
.on("click", function(event, d) {
if (!isExploded) {
explodeState(d.id);
} else if (explodedStateId === d.id) {
resetMap();
}
})
.on("mouseover", function(event, d) {
// Append a new path for the state border
if (zl > 2) return
g.append("path")
.attr("class", "state-border")
.attr("d", path(d))
.style("fill", "none")
.style("stroke", "blue")
.style("stroke-width", 2);
})
.on("mouseout", function(event, d) {
// Remove the state border
g.selectAll(".state-border").remove();
})
function clicked(event, d) {
const [[x0, y0], [x1, y1]] = path.bounds(d); // Get bounding box of the state
event.stopPropagation(); // Stop event bubbling
const dx = x1 - x0;
const dy = y1 - y0;
const x = (x0 + x1) / 2;
const y = (y0 + y1) / 2;
const scale = Math.min(8, 0.9 / Math.max(dx / 975, dy / 610)); // Set maximum zoom level to 8
const translate = [487.5 - scale * x, 305 - scale * y];
svg.transition().duration(750).call(
zoom.transform,
d3.zoomIdentity.translate(translate[0], translate[1]).scale(scale)
);
}
function explodeState(stateId) {
isExploded = true;
explodedStateId = stateId;
var exploder = d3.exploder()
.projection(projection)
.size(function(d) { if (!d) return; console.log(d); return d.id === stateId ? 100 : 0; })
.position(function(d) {
if (!d) return
if (d.id === stateId) {
return [width / 2, height / 2]; // Center the exploded state
}
return [-1000, -1000]; // Move other states out of view
});
g.selectAll("path")
.transition()
.duration(500)
.call(exploder);
}
function resetMap() {
isExploded = false;
explodedStateId = null;
var exploder = d3.exploder()
.projection(projection)
.size(function(d) { return 40; })
.position(function(d, i) {
var px = Math.max(0, width - 9*60)/2
return [px + (i%10)*60, 70 + Math.floor(i/10)*60];
});
g.selectAll("path")
.transition()
.duration(500)
.call(exploder);
}
let currentTransform = d3.zoomIdentity;
g.append("path")
.datum(topojson.mesh(us, us.objects.states, (a, b) => a !== b))
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-linejoin", "round")
.attr("d", path);
const countyBorders = g.append("g")
.attr("class", "counties")
.selectAll("path")
.data(counties.features)
.join("path")
.attr("d", path)
.style("fill", "rgba(255, 255, 255, 0.1)") // Light fill or transparent
.style("stroke", "#777")
.style("stroke-width", 0.5)
.style("opacity", 0)
.style("visibility", "hidden");
const zoom = d3.zoom()
.scaleExtent([1, 8])
.on('zoom', zoomed);
svg.call(zoom);
//svg.on("click", reset);
function reset() {
const translate = [0, 0]
const scale = 1
svg.transition().duration(750).call(
zoom.transform,
d3.zoomIdentity.translate(translate[0], translate[1]).scale(scale)
);
}
function zoomed(event) {
const transform = event.transform;
g.attr('transform', transform);
currentTransform = event.transform;
const zoomLevel = transform.k;
zl = zoomLevel
console.log(zl)
if (zoomLevel > 2) { // Adjust the threshold as needed
countyBorders.style("visibility", "visible").style("opacity", 1);
// Attach event listeners to counties
countyBorders.on("mouseover", countyMouseover)
.on("mouseout", countyMouseout);
} else {
countyBorders.style("visibility", "hidden").style("opacity", 0);
// Detach event listeners from counties
countyBorders.on("mouseover", null)
.on("mouseout", null);
}
const { visibleStates, visibleCounties } = calculateVisibleFeatures();
legend.text(`States: ${visibleStates}, Counties: ${visibleCounties}`);
}
function calculateVisibleFeatures() {
let visibleStates = 0;
let visibleCounties = 0;
states.features.forEach(d => {
if (isFeatureVisible(d)) visibleStates++;
});
counties.features.forEach(d => {
if (isFeatureVisible(d)) visibleCounties++;
});
return { visibleStates, visibleCounties };
}
function isFeatureVisible(feature) {
const centroid = path.centroid(feature);
const transformedCentroid = currentTransform.apply(centroid);
// Assuming the SVG dimensions are 975x610
return transformedCentroid[0] >= 0 && transformedCentroid[0] <= 975 &&
transformedCentroid[1] >= 0 && transformedCentroid[1] <= 610;
}
g.append("circle")
.attr("cx", longBeachCoords[0])
.attr("cy", longBeachCoords[1])
.attr("r", 5)
.style("fill", "red").raise()
function countyMouseover(event, d) {
// Style changes or tooltip display for county hover
d3.select(this)
.style("stroke", "blue")
.style("stroke-width", 1);
}
function countyMouseout(event, d) {
// Reset style or hide tooltip
d3.select(this)
.style("stroke", "#777")
.style("stroke-width", 0.5);
}
const initialVisible = calculateVisibleFeatures();
legend.text(`States: ${initialVisible.visibleStates}, Counties: ${initialVisible.visibleCounties}`);
return svg.node();
}