Steve Tom
Steve Tom

Reputation: 31

How to create variable number of image or circle elements in an SVG element using a loop in D3 js(v3)

I'm trying to make a pictogram using d3. So, I have a scaled value with a range from 1 to 10, and I want to create images or circles to represent the numbers, that is, 7 circles or images for number 7, 5 for number 5 etc. The image or circle tag should be drawn on a 'mousein' event. 'mouseout' event shouldn't remove the images. But every mousein event should redraw correct number of circles. Can someone demonstrate a simple example with the right approach?

Things I've tried: I'm able to append n image tags to the svg element but I'm only able to remove the last element since the variable gets the value of last circle drawn.

for(i=1;i<limit;i++){         // 1<=limit<=10 --> Scaled value on each mousein event
    var img = svg.append("image")
      .attr("x", i*width*1/8)
      .attr("y", height*1/10)
      .style("opacity", 1)
      .attr("width", 50)
      .attr("height", 50)
      .attr("xlink:href", "image.png")
      .attr("class", "image");    
    }

When I put all the elements in a group, the image is not being displayed but the elements are present in DOM and also shows the correct path to the image.

Upvotes: 0

Views: 392

Answers (1)

John
John

Reputation: 1341

The whole power of D3.js lies in its ability to bind data to display elements so you don't have to write loops to manually add items to the DOM.

This example demonstrates adding circles to the SVG element using the selectAll(), data(), enter() and remove() functions which are described in Mike Bostock's article Thinking in Joins.

const width = 500;
const height = 500;

let svg = d3.select('svg')


// create the rectangles to hover over
let rectangleHolders = svg.select('.rectangle-holder')
  .selectAll('rect')
  .data([1, 2, 3, 4, 5])
  .enter()
  // to add a rectangle with text, we need to add a group to hold both elements
  .append('g')
  // translate group to correct position
  .attr('transform', (d, i) => `translate(${i*40})`)

// add rectangle to each group
rectangleHolders.append('rect')
  .attr('height', 18)
  .attr('width', 18)
  // add event handler to rectangle to draw circles
  .on('mouseover', draw)

// add text to each group
rectangleHolders.append('text')
  .attr('dy','15')
  .attr('dx','10')
  .attr('text-anchor','middle')
  .text(d => d)

// function to draw circles
function draw(number) {

  console.log('draw', number)

  // create some dummy data in this case an
  // array of length 'count' full of nulls
  const data = Array(number)

  // select circle elements and bind data
  let circles = svg.select('.circle-holder').selectAll('circle')
    .data(data)

  // enter, append and set attributes
  circles.enter()
    .append('circle')
    .attr('cx', (d, i) => i * 20 + 10)
    .attr('cy', 10)
    .attr('r', 8)
    .attr('value', d => d)
    // add a tranisition to the opacity so the circles fade in
    .attr('opacity', 0)
    .transition()
    .attr('opacity', 1)

  // exit and remove unused elements (with transition)
  circles.exit()
    .transition()
    .attr('opacity', 0)
    .remove()
}
rect {
  fill: none;
  stroke: black;
}

body {
font-family:sans-serif;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg height="40" width="200">
<g class="rectangle-holder"></g>
<g class="circle-holder" transform="translate(0,20)"></g>
</svg>

Upvotes: 2

Related Questions