Search code examples
javascriptd3.js

How do I make individual labels for multiple data points in D3?


My data file is a .csv (data below) with one column called myletters. It has 3 rows with the value A, 6 rows with the value B, 11 rows with the value C, and about 18 rows with the value D.

I am experimenting/learning and trying to make individual circles for each letter value and place them in 4 different x,y coordinates based on the value (A,B,C, or D). This works. My problem is I want to create a label for A, B, C, or D but the .text is pulling in every instance of my value which causes the label to be progressively darker depending on how many instances of each letter. A is black but D is very, very heavy black.

    const margin = {left: 170, top: 50, bottom: 50, right: 20}
    const width = 1000 - margin.left - margin.right
    const height = 950 - margin.top - margin.bottom

    const coordinates = {
    'A': [300,150],
    'B': [450,150], 
    'C': [300,400],
    'D': [450,400] } ;    

    const svg = d3.select("#vis")
    .append('svg')
    .attr('width', 1000)
    .attr('height', 950)
    .attr('opacity', 1)
        
    const x = d3.scaleOrdinal()
    .domain([1, 2, 3,4])
    .range([150,300,450,550])

    const color = d3.scaleOrdinal()
    .domain([1, 2, 3,4])
    .range([ "#F8766D", "#00BA38", "#619CFF","#666666"])
 
      
    d3.csv('../data/testdata.csv').then(data => {
    const xAccessor = d => d.myletters

    const unique_value = [...new Set(data.map((item) => item.myletters))];

    const nodes = svg.selectAll("circle")
    .data(data)
    .enter()
    .append('circle')
    .style("fill", d=> color(xAccessor(d)))
    .style("fill-opacity", 0.8)
    .attr("stroke", "black")
    .attr('r', 7)
    .style("stroke-width", 1)

    simulation = d3.forceSimulation(data)
    simulation  
    .force('forceX',d3.forceX(d=> coordinates[xAccessor(d)][0])) //works
    .force('forceY',d3.forceY(d=> coordinates[xAccessor(d)][1])) //workd     
    .force("center", d3.forceCenter().x(width / 2).y(height / 2)) 
    .force("charge", d3.forceManyBody().strength(10)) 
    .force("collide", d3.forceCollide().strength(1).radius(8).iterations(5)) 
   
    .on("tick", function(d){
    nodes
    .attr('cx', d => d.x)
    .attr('cy', d => d.y)
          
    });

    //add label rectangle
     svg.selectAll('my_rect')
     .data(data)
     .enter()
     .append('rect')
    .attr('x',d=> coordinates[xAccessor(d)][0]) 
    .attr('y',d=> coordinates[xAccessor(d)][1]) 
    .attr('width',50)
    .attr('height', 30)
    .style("fill", d=> color(xAccessor(d)))
    .style("fill-opacity", 0.2)
    .attr("stroke", "#989898")
    const texts =  svg.selectAll('mytext')
    .data(data)
    .enter()
    .append('text')
         
    texts
                 
    .attr('x',d=> coordinates[xAccessor(d)][0]) 
    .attr('y',d=> coordinates[xAccessor(d)][1]) 
    .attr('font-size','12px')
    .attr('text-anchor','middle')
    .text(d => xAccessor(d))   

         
              
                     

    })
    //csv data
    myletters
    A
    A
    A
    B
    B
    B
    B
    B
    B
    C
    C
    C
    C
    C
    C
    C
    C
    C
    C
    C
    D
    D
    D
    D
    D
    D
    D
    D
    D
    D
    D
    D
    D
    D
    D
    D
    D
    D

I tried .data(unique_values) instead of .data(data) but that just lists A,B,C,D in each box.

enter image description here


Solution

  • You can achieve that by using unique_value. Here is the modified code:

    svg
      .selectAll("my_rect")
      .data(unique_value) // use unique_value
      .enter()
      .append("rect")
      .attr("x", (d) => coordinates[d][0]) // no longer use xAccessor
      .attr("y", (d) => coordinates[d][1]) // no longer use xAccessor
      .attr("width", 50)
      .attr("height", 30)
      .style("fill", (d) => color(d)) // no longer use xAccessor
      .style("fill-opacity", 0.2)
      .attr("stroke", "#989898");
    
    const texts = svg.selectAll("mytext")
      .data(unique_value) // use unique_value
      .enter()
      .append("text");
    
    texts
      .attr("x", (d) => coordinates[d][0]) // no longer use xAccessor
      .attr("y", (d) => coordinates[d][1]) // no longer use xAccessor
      .attr("font-size", "12px")
      .attr("text-anchor", "middle")
      .text((d) => d); // no longer use xAccessor