Created
December 3, 2025 13:37
-
-
Save leoimpett/90c6d63a9417389556ca00bf9eace751 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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