Skip to content

Instantly share code, notes, and snippets.

@leoimpett
Created December 3, 2025 13:37
Show Gist options
  • Select an option

  • Save leoimpett/90c6d63a9417389556ca00bf9eace751 to your computer and use it in GitHub Desktop.

Select an option

Save leoimpett/90c6d63a9417389556ca00bf9eace751 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3 Image Plot</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
body {
margin: 0;
font-family: system-ui, sans-serif;
}
#container {
position: relative;
}
svg {
display: block;
}
/* Tooltip */
#tooltip {
position: absolute;
pointer-events: none;
padding: 4px 8px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
font-size: 12px;
border-radius: 3px;
white-space: nowrap;
opacity: 0;
transition: opacity 0.1s ease-out;
}
.axis-label {
font-size: 14px;
font-weight: 600;
}
</style>
</head>
<body>
<!-- Optional axis labels, if you still want them -->
<div style="padding: 8px;">
<span id="x-axis-label" class="axis-label"></span>
<span id="y-axis-label" class="axis-label" style="margin-left: 16px;"></span>
</div>
<div id="container">
<svg id="image-svg"></svg>
<div id="tooltip"></div>
</div>
<script>
// ========= CONFIG =========
// Size of each image (both width + height)
const IMAGE_SIZE = 40; // <--- tweak this
// Path to your CSV
const CSV_PATH = "data/images.csv";
// Base path to your image files (image_name will be appended)
const IMAGE_BASE_PATH = "images/"; // e.g. "images/" or "./thumbnails/"
// If your x and y in the CSV are already in pixels, set this to false.
// If they are in [0,1] or some other range and you want to scale to the SVG size, set true.
const USE_SCALES = true;
// ========= AXIS LABELS (optional, from your old project) =========
const xAxisLabel = document.querySelector("#x-axis-label");
if (xAxisLabel) {
xAxisLabel.innerHTML = "X →";
xAxisLabel.className = "axis-label";
}
const yAxisLabel = document.querySelector("#y-axis-label");
if (yAxisLabel) {
yAxisLabel.innerHTML = "Y →";
yAxisLabel.className = "axis-label";
}
// ========= MAIN D3 CODE =========
const svg = d3.select("#image-svg");
const container = document.getElementById("container");
const tooltip = d3.select("#tooltip");
// Set SVG size (you can also make this responsive if you want)
const width = window.innerWidth;
const height = window.innerHeight - 40; // subtract a bit for labels, etc.
svg
.attr("width", width)
.attr("height", height);
// Load data
d3.csv(CSV_PATH, d3.autoType).then(data => {
// Expecting columns: image_name, x, y
let xScale, yScale;
if (USE_SCALES) {
const xExtent = d3.extent(data, d => d.x);
const yExtent = d3.extent(data, d => d.y);
// Leave a bit of padding so images aren't cut off
xScale = d3.scaleLinear()
.domain(xExtent)
.range([IMAGE_SIZE, width - IMAGE_SIZE]);
yScale = d3.scaleLinear()
.domain(yExtent)
.range([IMAGE_SIZE, height - IMAGE_SIZE]);
}
const images = svg
.selectAll("image")
.data(data)
.join("image")
.attr("href", d => IMAGE_BASE_PATH + d.image_name)
.attr("width", IMAGE_SIZE)
.attr("height", IMAGE_SIZE)
.attr("x", d => USE_SCALES ? xScale(d.x) - IMAGE_SIZE / 2 : d.x)
.attr("y", d => USE_SCALES ? yScale(d.y) - IMAGE_SIZE / 2 : d.y)
.on("mouseover", function(event, d) {
tooltip
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY + 10) + "px")
.text(d.image_name)
.style("opacity", 1);
})
.on("mousemove", function(event, d) {
// follow the cursor
tooltip
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY + 10) + "px");
})
.on("mouseout", function() {
tooltip.style("opacity", 0);
});
// If you want click behavior too, you can add it here:
// .on("click", (event, d) => {
// const url = IMAGE_BASE_PATH + d.image_name;
// window.open(url, "_blank");
// });
}).catch(err => {
console.error("Error loading CSV:", err);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment