javascriptd3.jsvisualizationobservablehq

Replacing colored circles with image circles using javascript d3 visualization


I am trying to replace the colored circles with image circles. The following plot is made using d3 in javascript. I cannot add image due to my less than 10 rating.

enter image description here

// https://observablehq.com/@d3/smooth-zooming@58


function _chart(width,height,d3,data,radius)
{
  let currentTransform = [width / 2, height / 2, height];

  const svg = d3.create("svg")
      .attr("viewBox", [0, 0, width, height])

  const g = svg.append("g");

  g.selectAll("circle")
    .data(data)
    .join("circle")
      .attr("cx", ([x]) => x)
      .attr("cy", ([, y]) => y)
      .attr("r", radius)
      .attr("fill", (d, i) => d3.interpolateRainbow(i/360))

  function transition() {
    const d = data[Math.floor(Math.random() * data.length)];
    const i = d3.interpolateZoom(currentTransform, [...d, radius * 2 + 1]);

    g.transition()
        .delay(250)
        .duration(i.duration)
        .attrTween("transform", () => t => transform(currentTransform = i(t)))
        .on("end", transition);
  }

  function transform([x, y, r]) {
    return `
      translate(${width / 2}, ${height / 2})
      scale(${height / r})
      translate(${-x}, ${-y})
    `;
  }

  return svg.call(transition).node();
}


function _height(){return(
500
)}

function _radius(){return(
  2
)}

function _step(radius){return(
radius * 2
)}

function _data(step,theta,width,height){return(
Array.from({length: 2000}, (_, i) => {
  const r = step * Math.sqrt(i += 0.5), a = theta * i;
  return [
    width / 2 + r * Math.cos(a),
    height / 2 + r * Math.sin(a)
  ];
})
)}

function _theta(){return(
Math.PI * (3 - Math.sqrt(5))
)}

function _d3(require){return(
require("d3@6")
)}

export default function define(runtime, observer) {
  const main = runtime.module();
  
  main.variable(observer("chart")).define("chart", ["width","height","d3","data","radius"], _chart);
  main.variable().define("height", _height);
  main.variable().define("radius", _radius);
  main.variable().define("step", ["radius"], _step);
  main.variable().define("data", ["step","theta","width","height"], _data);
  main.variable().define("theta", _theta);
  main.variable().define("d3", ["require"], _d3);
  return main;
}

I think that we might need to alter the data function. I was not able to show it with images as I am a beginner in Javascript.


Solution

  • First off, your immediate question is:

    I am trying to replace the colored circles with image circles.

    This has been answered a number of times on StackOverflow, for example here.

    Doing so in the context of d3 is a bit different, though, since you'll probably want some code along the lines of:

    let url = ...
    let defs = svg.append("defs");
    defs
      .append("pattern")
      ...
      .attr("id", 'image')
      .append("image")
      ...
      .attr("href", url);
    

    There are more details on this below.

    Also, the code block that you've posted looks to me like it was returned by the Observalbe embedding API, i.e.: https://api.observablehq.com/@d3/smooth-zooming.js?v=3

    I've forked that notebook and made the changes necessary to include a few images. Here's the fork and here's the proper way to embed that code:

    <div id="observablehq-chart-25b97cbc"></div>
    <script type="module">
    import {Runtime, Inspector} from "https://cdn.jsdelivr.net/npm/@observablehq/runtime@5/dist/runtime.js";
    import define from "https://api.observablehq.com/d/adfba0cfb4396df0.js?v=3";
    new Runtime().module(define, name => {
      if (name === "chart") return new Inspector(document.querySelector("#observablehq-chart-25b97cbc"));
    });
    </script>

    If you examine the forked notebook, you should notice a code block near the top that looks something like so:

    image_urls = [
      "https://upload.wikimedia.org/wikipedia/commons/3/3d/Perspectiva_Corporum_Regularium_36a.jpg",
      "https://upload.wikimedia.org/wikipedia/commons/7/7f/Hexaeder_NdFeB_5041.jpg",
      "https://upload.wikimedia.org/wikipedia/commons/f/f6/Hexader_1.jpg"
    ]
    

    I guess that could be just about any list of publicly accessible URLs pointing to images.

    The next code block, that defines the chart, contains a few lines that look like:

    let defs = svg.append("defs");
    image_urls.forEach(function (url, i) {
      defs
        .append("pattern")
        .attr("id", `image${i}`)
        .attr("x", "0%")
        .attr("y", "0%")
        .attr("width", "100%")
        .attr("height", "100%")
        .attr("viewBox", "50 50 200 100")
        .append("image")
        .attr("x", "0%")
        .attr("y", "0%")
        .attr("width", "100%")
        .attr("height", "100%")
        .attr("href", url);
    });
    

    That sets up the patterns that you can use as fills. Finally, that bit is used later when the circles are joined:

    g.selectAll("circle")
      .data(data)
      .join("circle")
      ...
      .attr("fill", (_, i) => `url(#image${i % image_urls.length}`);