D3 Map XML Polygon ids to JSON data

Viewed 55

I have an svg/xml file that I'm loading into d3 using d3.xml(...)

This is the output of the SVG

I also have a small JSON dataset

const data = [{
    "id": "polygon5256", //roof
    "value": 39.5
  },
  {
    "id": "polygon1628",  //grass
    "value": 3.93
  },
  {
    "id": "polygon5254", //left wall
    "value": 3.14
  },
  {
    "id": "path5894", //door step
    "value": 20.98
  }
]

I'm trying to join the id's from the JSON dataset to the respective id's (polygons and paths) in the XML data. Do you know how I could also limit the selectable paths/polygons to only those that are in the JSON data? So only the available IDs will be affected by the mouse events?

Here is my jsfiddle

Any help would be much appreciated

1 Answers

In your fiddle you have d3.select('#polygon5256') but you can add events to any polygon with selectAll and a different CSS selector:

d3.selectAll("svg polygon")

You want path too (for the door step) so the CSS selector you need is:

d3.selectAll("svg polygon, svg path")

In the handler you can get the id of the polygon with (since you use d3 v5.9.2):

d3.event.target.id

Note that if you're using a later version of the D3 library this handling will need to change (see D3 v6 changes)

I've changed your fiddle to have a single visible div where the mouseover and mouseout handlers just populate labelling if available in data or clear the text.

Working example:

const data = [
  { "id": "polygon5256", "value": 39.5 }, // roof
  { "id": "polygon1628", "value": 3.93 }, // grass
  { "id": "polygon5254", "value": 3.14 }, // left wall
  { "id": "path5894", "value": 20.98 } // door step
];


// just make one visible div and leave text till later
const tooltip = d3.select("body")
  .append("div")
  .style("position", "absolute")
  .style("z-index", "10")
  .style("visibility", "visible");

const svgUrl = "https://raw.githubusercontent.com/gangrel11/samplefiles/main/house.svg";
d3.xml(svgUrl).then(render);

// on render set an event for any polygon of the svg
function render(data) {

  d3.select("body").node().append(data.documentElement)
  d3.selectAll("svg polygon, svg path")
    .on("mouseover", mouseover)
    .on("mouseout", mouseout);
    
}

// set the text of the div if it's in the data set
function mouseover(d) {
  const id = d3.event.target.id;
  const obj = data.find(item => item.id === id);
  const text = obj ? `${id} - ${obj.value}` : "";
  tooltip.text(text);
  d3.select(`#${id}`).style("opacity", 0.5);
}

// clear the text of the div
function mouseout(d) {
  const id = d3.event.target.id;
  tooltip.text("");
  d3.select(`#${id}`).style("opacity", 1);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Edit

If you want to limit the paths/polygons to only those that are in the JSON data you can swap out this:

function render(data) {

  d3.select("body").node().append(data.documentElement)
  d3.selectAll("svg polygon, svg path")
    .on("mouseover", mouseover)
    .on("mouseout", mouseout);
    
}

For this (note the function signature of render has to change as we refer to data array within the function):

function render(svg) {

  // add svg
  d3.select("body").node().append(svg.documentElement)
  
  // add events for ids  
  const ids = data.map(d => d.id);
  ids.forEach(id => d3.select(`#${id}`)
    .on("mouseover", mouseover)
    .on("mouseout", mouseout)
  );
  
}

Working example:

const data = [
  { "id": "polygon5256", "value": 39.5 }, // roof
  { "id": "polygon1628", "value": 3.93 }, // grass
  { "id": "polygon5254", "value": 3.14 }, // left wall
  { "id": "path5894", "value": 20.98 } // door step
];

// just make one visible div and leave text till later
const tooltip = d3.select("body")
  .append("div")
  .style("position", "absolute")
  .style("z-index", "10")
  .style("visibility", "visible");

// load external svg and render
const svgUrl = "https://raw.githubusercontent.com/gangrel11/samplefiles/main/house.svg";
d3.xml(svgUrl).then(render);

// on render set an event for anything with an id within data
function render(svg) {

  // add svg
  d3.select("body").node().append(svg.documentElement)
  
  // add events for ids  
  const ids = data.map(d => d.id);
  ids.forEach(id => d3.select(`#${id}`)
    .on("mouseover", mouseover)
    .on("mouseout", mouseout)
  );
  
}

// set the text of the div if it's in the data set
function mouseover(d) {
  const id = d3.event.target.id;
  const obj = data.find(item => item.id === id);
  const text = obj ? `${id} - ${obj.value}` : "";
  tooltip.text(text);
  d3.select(`#${id}`).style("opacity", 0.5);
}

// clear the text of the div
function mouseout(d) {
  const id = d3.event.target.id;
  tooltip.text("");
  d3.select(`#${id}`).style("opacity", 1);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Related