James
James

Reputation: 367

Adding circles to multi line graph with dropdown on d3.js

I am trying to add circles to the data points on the following line graph example: https://bl.ocks.org/ProQuestionAsker/8382f70af7f4a7355827c6dc4ee8817d

To generate the circles I have used the following:

svg.selectAll("dot")
  .data(data)
  .enter().append("circle")
  .attr("r", 3)
  .attr("color", "pink")
  .attr("cx", function(d) { return x(d.Month); })
  .attr("cy", function(d) { return y(+d.Sales); });

However, as seen here, all the circles for each every fruit. I would like only the circles for selected fruits to appear, as per the lines.

Many thanks James

Upvotes: 2

Views: 880

Answers (1)

Shashank
Shashank

Reputation: 5670

You see the circles for each and every fruit because you're not filtering the data based on the dropdown selection.

Here's a snippet doing that data filtering and appending the dots:

var dataAsCsv = `Month,Sales,Fruit,Year
Jan,87,strawberry,2016
Feb,3,strawberry,2016
Mar,89,strawberry,2016
Apr,56,strawberry,2016
May,1,strawberry,2016
Jun,17,strawberry,2016
Jul,59,strawberry,2016
Aug,43,strawberry,2016
Sep,16,strawberry,2016
Oct,94,strawberry,2016
Nov,99,strawberry,2016
Dec,53,strawberry,2016
Jan,93,grape,2016
Feb,8,grape,2016
Mar,95,grape,2016
Apr,62,grape,2016
May,5,grape,2016
Jun,24,grape,2016
Jul,62,grape,2016
Aug,49,grape,2016
Sep,18,grape,2016
Oct,101,grape,2016
Nov,103,grape,2016
Dec,53,grape,2016
Jan,94,blueberry,2016
Feb,15,blueberry,2016
Mar,95,blueberry,2016
Apr,64,blueberry,2016
May,11,blueberry,2016
Jun,33,blueberry,2016
Jul,64,blueberry,2016
Aug,53,blueberry,2016
Sep,27,blueberry,2016
Oct,103,blueberry,2016
Nov,108,blueberry,2016
Dec,62,blueberry,2016
Jan,80,strawberry,2015
Feb,0,strawberry,2015
Mar,71,strawberry,2015
Apr,51,strawberry,2015
May,3,strawberry,2015
Jun,11,strawberry,2015
Jul,56,strawberry,2015
Aug,34,strawberry,2015
Sep,12,strawberry,2015
Oct,75,strawberry,2015
Nov,94,strawberry,2015
Dec,46,strawberry,2015
Jan,76,grape,2015
Feb,0,grape,2015
Mar,78,grape,2015
Apr,58,grape,2015
May,10,grape,2015
Jun,22,grape,2015
Jul,47,grape,2015
Aug,36,grape,2015
Sep,18,grape,2015
Oct,86,grape,2015
Nov,98,grape,2015
Dec,40,grape,2015
Jan,79,blueberry,2015
Feb,0,blueberry,2015
Mar,78,blueberry,2015
Apr,49,blueberry,2015
May,5,blueberry,2015
Jun,31,blueberry,2015
Jul,62,blueberry,2015
Aug,49,blueberry,2015
Sep,7,blueberry,2015
Oct,86,blueberry,2015
Nov,100,blueberry,2015
Dec,46,blueberry,2015`;


// Set the margins
var margin = {top: 60, right: 100, bottom: 20, left: 80},
  width = 850 - margin.left - margin.right,
  height = 370 - margin.top - margin.bottom;

// Parse the month variable
var parseMonth = d3.timeParse("%b");
var formatMonth = d3.timeFormat("%b");

var formatYear = d3.timeFormat("%Y");
var parseYear = d3.timeParse("%Y");


// Set the ranges
var x = d3.scaleTime().domain([parseMonth("Jan"), parseMonth("Dec")]).range([0, width]);
var y = d3.scaleLinear().range([height, 0]);


// Define the line
var valueLine = d3.line()
    .x(function(d) { return x(d.Month); })
    .y(function(d) { return y(+d.Sales); })

// Create the svg canvas in the "graph" div
var svg = d3.select("#graph")
        .append("svg")
        .style("width", width + margin.left + margin.right + "px")
        .style("height", height + margin.top + margin.bottom + "px")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform","translate(" + margin.left + "," + margin.top + ")")
        .attr("class", "svg");


var data = d3.csvParse(dataAsCsv);
  
   // Format the data
  data.forEach(function(d) {
      d.Month = parseMonth(d.Month);
      d.Sales = +d.Sales;
      d.Fruit = d.Fruit;
      d.Year = formatYear(parseYear(+d.Year));
  });

  var nest = d3.nest()
	  .key(function(d){
	    return d.Fruit;
	  })
	  .key(function(d){
	  	return d.Year;
	  })
	  .entries(data)

  // Scale the range of the data
  x.domain(d3.extent(data, function(d) { return d.Month; }));
  y.domain([0, d3.max(data, function(d) { return d.Sales; })]);
  
  // Set up the x axis
  var xaxis = svg.append("g")
       .attr("transform", "translate(0," + height + ")")
       .attr("class", "x axis")
       .call(d3.axisBottom(x)
          .ticks(d3.timeMonth)
          .tickSize(0, 0)
          .tickFormat(d3.timeFormat("%B"))
          .tickSizeInner(0)
          .tickPadding(10));

  // Add the Y Axis
   var yaxis = svg.append("g")
       .attr("class", "y axis")
       .call(d3.axisLeft(y)
          .ticks(5)
          .tickSizeInner(0)
          .tickPadding(6)
          .tickSize(0, 0));
  
  // Add a label to the y axis
  svg.append("text")
        .attr("transform", "rotate(-90)")
        .attr("y", 0 - 60)
        .attr("x", 0 - (height / 2))
        .attr("dy", "1em")
        .style("text-anchor", "middle")
        .text("Monthly Sales")
        .attr("class", "y axis label");
    
  svg.append('g').classed('data-points', true);
  
  // Create a dropdown
    var fruitMenu = d3.select("#fruitDropdown")

    fruitMenu
		.append("select")
		.selectAll("option")
        .data(nest)
        .enter()
        .append("option")
        .attr("value", function(d){
            return d.key;
        })
        .text(function(d){
            return d.key;
        })


 
 	// Function to create the initial graph
 	var initialGraph = function(fruit){

 		// Filter the data to include only fruit of interest
 		var selectFruit = nest.filter(function(d){
                return d.key == fruit;
              })

	    var selectFruitGroups = svg.selectAll(".fruitGroups")
		    .data(selectFruit, function(d){
		      return d ? d.key : this.key;
		    })
		    .enter()
		    .append("g")
		    .attr("class", "fruitGroups")

		var initialPath = selectFruitGroups.selectAll(".line")
			.data(function(d) { return d.values; })
			.enter()
			.append("path")

		initialPath
			.attr("d", function(d){
				return valueLine(d.values)
			})
			.attr("class", "line")
      
   svg.select('g.data-points').selectAll("dot")
    .data(data.filter(function(d) { 
    	return d.Fruit === fruit;
    }))
    .enter().append("circle").classed('dot', true)
    .attr("r", 3)
    .style("fill", "pink").style('stroke', '#000')
    .attr("cx", function(d) { return x(d.Month); })
    .attr("cy", function(d) { return y(+d.Sales); });

 	}

 	// Create initial graph
 	initialGraph("strawberry")


 	// Update the data
 	var updateGraph = function(fruit){

 		// Filter the data to include only fruit of interest
 		var selectFruit = nest.filter(function(d){
                return d.key == fruit;
              })

 		// Select all of the grouped elements and update the data
	    var selectFruitGroups = svg.selectAll(".fruitGroups")
		    .data(selectFruit)

		    // Select all the lines and transition to new positions
            selectFruitGroups.selectAll("path.line")
               .data(function(d){
                  return (d.values);
                })
                .transition()
                  .duration(1000)
                  .attr("d", function(d){
                    return valueLine(d.values)
                  });
                  
   var circles = svg.select('g.data-points').selectAll(".dot")
    .data(data.filter(function(d) { 
    	return d.Fruit === fruit;
    }));

	circles
    .enter().append("circle")
    .merge(circles).classed('data-point', true)
    .attr("r", 3)
    .style("fill", "pink").style('stroke', '#000')
    .transition().duration(1000)
    .attr("cx", function(d) { return x(d.Month); })
    .attr("cy", function(d) { return y(+d.Sales); });
    
  }


 	// Run update function when dropdown selection changes
 	fruitMenu.on('change', function(){

 		// Find which fruit was selected from the dropdown
 		var selectedFruit = d3.select(this)
            .select("select")
            .property("value")

        // Run update function with the selected fruit
        updateGraph(selectedFruit)


    });
.line {
  fill: none;
  stroke: #EF5285;
  stroke-width: 2px;
}
<script src="https://d3js.org/d3.v4.js"></script>

    
    <div id = "fruitDropdown"></div>
    <div id="graph"></div>

Important code changes:

  1. Instead of appending circles directly to the SVG, I've created a group <g class="data-points"></g> that holds all the dots.

    svg.append('g').classed('data-points', true);
    
  2. Enter/update/exit all dots within the above group in both functions i.e. initialGraph and updateGraph

    InitialGraph:

    svg.select('g.data-points').selectAll("dot")
      .data(data.filter(function(d) { 
        return d.Fruit === fruit;
      }))
     .enter().append("circle").classed('dot', true)
     .attr("r", 3)
     .style("fill", "pink").style('stroke', '#000')
     .attr("cx", function(d) { return x(d.Month); })
     .attr("cy", function(d) { return y(+d.Sales); });
    

    UpdateGraph:

    var circles = svg.select('g.data-points').selectAll(".dot")
      .data(data.filter(function(d) { 
        return d.Fruit === fruit;
      }));
    
    circles
      .enter().append("circle")
      .merge(circles).classed('data-point', true)
      .attr("r", 3)
      .style("fill", "pink").style('stroke', '#000')
      .transition().duration(1000)
      .attr("cx", function(d) { return x(d.Month); })
      .attr("cy", function(d) { return y(+d.Sales); });
    

Observe the data filtering based on the fruit selected bound to the circles and the transition applied in order to match the transition for the lines.

  1. Always use style for applying fill and not attr. It's a good practice. Adding color:pink wouldn't change the color of the circles but fill would. In addition, I've added a stroke in order to make them visible even with a pink color. You can always change that though.

I would suggest you to add a code snippet every time you ask a question and not provide links. That would be easier for anyone to debug and help fix errors.

Hope this helps. :)

Upvotes: 2

Related Questions