【问题标题】:Make circles match same color filtering as multi-line on d3.js?使圆圈与 d3.js 上的多行匹配相同的颜色过滤?
【发布时间】:2018-01-09 21:35:44
【问题描述】:

我有一个多线图,当按每个水果过滤时会更新。每条线的颜色对应不同的销售年份。在Shashank 的帮助下,每个数据点线上的圆圈已添加到组中,而不是直接附加到 SVG。这是一个显示这个的sn-p:

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;

//Legend sizing
var legendRectSize = 18;                                 
var legendSpacing = 4; 


// 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]);

var colors = d3.scaleOrdinal()
  .domain(["2016", "2015"])
  .range(["#00BFFF", "#87CEFA"]);
  
// Define the line
var valueLine = d3.line()
    .x(function(d) { return x(d.Month); })
    .y(function(d) { return y(+d.Sales); })
	
// Define the div for the tooltip
var div = d3.select("body").append("div")	
    .attr("class", "tooltip")				
    .style("opacity", 0);

// 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")
	   .style("font-family", "Courier New")
       .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")
	   .style("font-family", "Courier New")
       .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")
		.style("font-family", "Courier New")
		.style("font-size", "11px");
		
	//Add Title
  svg.append("text")
        .attr("y", 0)
        .attr("x", width/2)
        .attr("dy", "1em")
        .style("text-anchor", "middle")
        .text("Fruit Sales")
        .attr("class", "y axis label")
		.style("font-family", "Courier New")
		.style("font-size", "20px");
  
  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")
			.attr("stroke", function(d){ return colors(d.key)});


		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)	
			.attr("fill", function(d){ return colors(d.key)})
			.attr("cx", function(d) { return x(d.Month); })
			.attr("cy", function(d) { return y(+d.Sales); })
				.on("mouseover", function(d) {
					div.transition()		
						.duration(200)		
						.style("opacity", .9);		
					div	.html("Sales:" + " "  + d.Sales)	
						.style("font-family", "Courier New")
						.style("left", (d3.event.pageX) + "px")		
						.style("top", (d3.event.pageY - 28) + "px");	
					})					
				.on("mouseout", function(d) {		
					div.transition()		
						.duration(500)		
						.style("opacity", 0);	
				});	
 	}

 	// 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)
    .attr("fill", function(d){ return colors(d.key)})
    .transition().duration(1000)
    .attr("cx", function(d) { return x(d.Month); })
    .attr("cy", function(d) { return y(+d.Sales); });
    
  }
  
  var legend = svg.selectAll('.legend')                     
          .data(colors.domain())                                   // NEW
          .enter()                                                // NEW
          .append('g')                                            // NEW
          .attr('class', 'legend')                                // NEW
          .attr('transform', function(d, i) {                     // NEW
            var height1 = legendRectSize + legendSpacing;          // NEW
            var offset =  height1 * colors.domain().length /2;     // NEW
            var horz = 37 * legendRectSize;                       // NEW
            var vert = i * height1 - offset;                       // NEW
            return 'translate(' + horz + ',' + vert + ')';        // NEW
          });                                                     // NEW

        legend.append('rect')                                     // NEW
          .attr('width', legendRectSize)                          // NEW
          .attr('height', legendRectSize)                         // NEW
          .style('fill', colors)                                   // NEW
          .style('stroke', colors);                                // NEW
          
        legend.append('text')                                     // NEW
          .attr('x', legendRectSize + legendSpacing)              // NEW
          .attr('y', legendRectSize - legendSpacing) 
		  .style("font-family", "Courier New")
          .text(function(d) { return d; });    


 	// 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-width: 2px;
}

div.tooltip {	
    position: absolute;			
    text-align: center;			
    width: 60px;					
    height: 28px;					
    padding: 2px;				
    font: 12px sans-serif;		
    background: lightsteelblue;	
    border: 0px;		
    border-radius: 8px;			
    pointer-events: none;			
}

.dot:hover {
      fill: black;
    }
	
.legend {                                                   /* NEW */
        font-size: 10px;                                          /* NEW */
      }                                                           /* NEW */
      rect {                                                      /* NEW */
        stroke-width: 2;                                          /* NEW */
      }                    
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>D3 Page Template</title>
        <script src="https://d3js.org/d3.v4.js"></script>
        <link rel="stylesheet" href="style.css">
    </head>
  
  <body>
	<g class="data-points"></g>
    <div id = "fruitDropdown"></div>
    <div id="graph"></div>
	
    <script src="Example11.js"></script>
  </body>
  
</html>

如何确保圆圈采用与线条相同的颜色,同时确保在选择不同水果时它们会更新?我希望这个解决方案也能为图例中的额外矩形提供修复。

非常感谢, 詹姆斯

【问题讨论】:

    标签: javascript d3.js svg


    【解决方案1】:

    将圈子的fillcolors(d.key) 更改为colors(d.Year) 就成功了。

    代码更改和sn-p:

    .attr("fill", function(d){ return colors(d.Year)})
    

    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;
    
    //Legend sizing
    var legendRectSize = 18;                                 
    var legendSpacing = 4; 
    
    
    // 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]);
    
    var colors = d3.scaleOrdinal()
      .domain(["2016", "2015"])
      .range(["#00BFFF", "#87CEFA"]);
      
    // Define the line
    var valueLine = d3.line()
        .x(function(d) { return x(d.Month); })
        .y(function(d) { return y(+d.Sales); })
    	
    // Define the div for the tooltip
    var div = d3.select("body").append("div")	
        .attr("class", "tooltip")				
        .style("opacity", 0);
    
    // 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")
    	   .style("font-family", "Courier New")
           .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")
    	   .style("font-family", "Courier New")
           .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")
    		.style("font-family", "Courier New")
    		.style("font-size", "11px");
    		
    	//Add Title
      svg.append("text")
            .attr("y", 0)
            .attr("x", width/2)
            .attr("dy", "1em")
            .style("text-anchor", "middle")
            .text("Fruit Sales")
            .attr("class", "y axis label")
    		.style("font-family", "Courier New")
    		.style("font-size", "20px");
      
      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")
    			.attr("stroke", function(d){ return colors(d.key)});
    
    
    		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)	
    			.attr("fill", function(d){ return colors(d.Year)})
    			.attr("cx", function(d) { return x(d.Month); })
    			.attr("cy", function(d) { return y(+d.Sales); })
    				.on("mouseover", function(d) {
    					div.transition()		
    						.duration(200)		
    						.style("opacity", .9);		
    					div	.html("Sales:" + " "  + d.Sales)	
    						.style("font-family", "Courier New")
    						.style("left", (d3.event.pageX) + "px")		
    						.style("top", (d3.event.pageY - 28) + "px");	
    					})					
    				.on("mouseout", function(d) {		
    					div.transition()		
    						.duration(500)		
    						.style("opacity", 0);	
    				});	
     	}
    
     	// 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)
        .attr("fill", function(d){ return colors(d.Year)})
        .transition().duration(1000)
        .attr("cx", function(d) { return x(d.Month); })
        .attr("cy", function(d) { return y(+d.Sales); });
        
      }
      
      var legend = svg.selectAll('.legend')                     
              .data(colors.domain())                                   // NEW
              .enter()                                                // NEW
              .append('g')                                            // NEW
              .attr('class', 'legend')                                // NEW
              .attr('transform', function(d, i) {                     // NEW
                var height1 = legendRectSize + legendSpacing;          // NEW
                var offset =  height1 * colors.domain().length /2;     // NEW
                var horz = 37 * legendRectSize;                       // NEW
                var vert = i * height1 - offset;                       // NEW
                return 'translate(' + horz + ',' + vert + ')';        // NEW
              });                                                     // NEW
    
            legend.append('rect')                                     // NEW
              .attr('width', legendRectSize)                          // NEW
              .attr('height', legendRectSize)                         // NEW
              .style('fill', colors)                                   // NEW
              .style('stroke', colors);                                // NEW
              
            legend.append('text')                                     // NEW
              .attr('x', legendRectSize + legendSpacing)              // NEW
              .attr('y', legendRectSize - legendSpacing) 
    		  .style("font-family", "Courier New")
              .text(function(d) { return d; });    
    
    
     	// 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-width: 2px;
    }
    
    div.tooltip {	
        position: absolute;			
        text-align: center;			
        width: 60px;					
        height: 28px;					
        padding: 2px;				
        font: 12px sans-serif;		
        background: lightsteelblue;	
        border: 0px;		
        border-radius: 8px;			
        pointer-events: none;			
    }
    
    .dot:hover {
          fill: black;
        }
    	
    .legend {                                                   /* NEW */
            font-size: 10px;                                          /* NEW */
          }                                                           /* NEW */
          rect {                                                      /* NEW */
            stroke-width: 2;                                          /* NEW */
          }
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="utf-8">
            <title>D3 Page Template</title>
            <script src="https://d3js.org/d3.v4.js"></script>
            <link rel="stylesheet" href="style.css">
        </head>
      
      <body>
    	<g class="data-points"></g>
        <div id = "fruitDropdown"></div>
        <div id="graph"></div>
    	
        <script src="Example11.js"></script>
      </body>
      
    </html>

    另外,如果您有任何建议,我希望您将fruitGroups 和相应的circles 安排在同一组内。我建议你的结构如下:

    <g class="fruitGroups">
       <g class="2015">
         <path></path>
         <circle class="dot"></circle>
         ...
       </g>
       <g class="2016">
         <path></path>
         <circle></circle>
         ...
       </g>
    </g>
    

    根据评论进行编辑

    如您在 cmets 中提到的那样,为了更改水果的颜色,我使用了单独的颜色域,下面是它用于确定 updateGraph 函数中 path 的颜色的方法。

    对于path

    .style('stroke', function(d) { if (fruit == "strawberry") {return colors(d.key)} else if (fruit == "grape") {return colors1(d.key)} else {return colors2(d.key)}});
    

    对于圈子:

    .style("fill", function(d){ if (d.Fruit == "strawberry") {return colors(d.Year)} else if (d.Fruit == "grape") {return colors1(d.Year)} else {return colors2(d.Year)}})
    

    对于传说:

    // rerender legend rects
    svg.selectAll('.legend rect')
      .style('fill', function(d) { if (fruit == "strawberry") {return colors(d)} else if (fruit == "grape") {return colors1(d)} else {return colors2(d)}})
      .style('stroke', function(d) { if (fruit == "strawberry") {return colors(d)} else if (fruit == "grape") {return colors1(d)} else {return colors2(d)}});
    

    这是一个执行上述所有操作的 sn-p:

    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;
    
    //Legend sizing
    var legendRectSize = 18;                                 
    var legendSpacing = 4; 
    
    
    // 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]);
    
    var colors = d3.scaleOrdinal()
      .domain(["2016", "2015"])
      .range(["#00BFFF", "#87CEFA"]);
      
    var colors1 = d3.scaleOrdinal()
      .domain(["2016", "2015"])
      .range(["red", "orange"]);
    
    var colors2 = d3.scaleOrdinal()
      .domain(["2016", "2015"])
      .range(["darkgreen", "lightgreen"]);
    
    // Define the line
    var valueLine = d3.line()
        .x(function(d) { return x(d.Month); })
        .y(function(d) { return y(+d.Sales); })
    	
    // Define the div for the tooltip
    var div = d3.select("body").append("div")	
        .attr("class", "tooltip")				
        .style("opacity", 0);
    
    // 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")
    	   .style("font-family", "Courier New")
           .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")
    	   .style("font-family", "Courier New")
           .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")
    		.style("font-family", "Courier New")
    		.style("font-size", "11px");
    		
    	//Add Title
      svg.append("text")
            .attr("y", 0)
            .attr("x", width/2)
            .attr("dy", "1em")
            .style("text-anchor", "middle")
            .text("Fruit Sales")
            .attr("class", "y axis label")
    		.style("font-family", "Courier New")
    		.style("font-size", "20px");
      
      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")
    			.attr("stroke", function(d){ if (fruit == "strawberry") {return colors(d.key)} else if (fruit == "grape") {return colors1(d.key)} else {return colors2(d.key)}});
    
    
    		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)	
    			.attr("fill", function(d){ if (d.Fruit == "strawberry") {return colors(d.Year)} else if (d.Fruit == "grape") {return colors1(d.Year)} else {return colors2(d.Year)}})
    			.attr("cx", function(d) { return x(d.Month); })
    			.attr("cy", function(d) { return y(+d.Sales); })
    				.on("mouseover", function(d) {
    					div.transition()		
    						.duration(200)		
    						.style("opacity", .9);		
    					div	.html("Sales:" + " "  + d.Sales)	
    						.style("font-family", "Courier New")
    						.style("left", (d3.event.pageX) + "px")		
    						.style("top", (d3.event.pageY - 28) + "px");	
    					})					
    				.on("mouseout", function(d) {		
    					div.transition()		
    						.duration(500)		
    						.style("opacity", 0);	
    				});	
     	}
    
     	// 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)
                      }).style('stroke', function(d) { if (fruit == "strawberry") {return colors(d.key)} else if (fruit == "grape") {return colors1(d.key)} else {return colors2(d.key)}});
                      
       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)
        .transition().duration(1000)
        .style("fill", function(d){ if (d.Fruit == "strawberry") {return colors(d.Year)} else if (d.Fruit == "grape") {return colors1(d.Year)} else {return colors2(d.Year)}})
        .attr("cx", function(d) { return x(d.Month); })
        .attr("cy", function(d) { return y(+d.Sales); });
        
        // rerender legend rects
        svg.selectAll('.legend rect')
          .style('fill', function(d) { if (fruit == "strawberry") {return colors(d)} else if (fruit == "grape") {return colors1(d)} else {return colors2(d)}})
          .style('stroke', function(d) { if (fruit == "strawberry") {return colors(d)} else if (fruit == "grape") {return colors1(d)} else {return colors2(d)}});
      }
      
      var legend = svg.selectAll('.legend')                     
              .data(colors.domain())                                   // NEW
              .enter()                                                // NEW
              .append('g')                                            // NEW
              .attr('class', 'legend')                                // NEW
              .attr('transform', function(d, i) {                     // NEW
                var height1 = legendRectSize + legendSpacing;          // NEW
                var offset =  height1 * colors.domain().length /2;     // NEW
                var horz = 37 * legendRectSize;                       // NEW
                var vert = i * height1 - offset;                       // NEW
                return 'translate(' + horz + ',' + vert + ')';        // NEW
              });                                                     // NEW
    
            legend.append('rect')                                     // NEW
              .attr('width', legendRectSize)                          // NEW
              .attr('height', legendRectSize)                         // NEW
              .style('fill', colors)                                   // NEW
              .style('stroke', colors);                                // NEW
              
            legend.append('text')                                     // NEW
              .attr('x', legendRectSize + legendSpacing)              // NEW
              .attr('y', legendRectSize - legendSpacing) 
    		  .style("font-family", "Courier New")
              .text(function(d) { return d; });    
    
    
     	// 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-width: 2px;
    }
    
    div.tooltip {	
        position: absolute;			
        text-align: center;			
        width: 60px;					
        height: 28px;					
        padding: 2px;				
        font: 12px sans-serif;		
        background: lightsteelblue;	
        border: 0px;		
        border-radius: 8px;			
        pointer-events: none;			
    }
    
    .dot:hover {
          fill: black;
        }
    	
    .legend {                                                   /* NEW */
            font-size: 10px;                                          /* NEW */
          }                                                           /* NEW */
          rect {                                                      /* NEW */
            stroke-width: 2;                                          /* NEW */
          }
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="utf-8">
            <title>D3 Page Template</title>
            <script src="https://d3js.org/d3.v4.js"></script>
            <link rel="stylesheet" href="style.css">
        </head>
      
      <body>
    	<g class="data-points"></g>
        <div id = "fruitDropdown"></div>
        <div id="graph"></div>
    	
        <script src="Example11.js"></script>
      </body>
      
    </html>

    您可以进行的改进:

    1. 色域:)
    2. 在转换paths 后渲染图例(使用.on('end, function(){}) 回调。如果您不确定如何使用它,请参考我的答案:Transition on end callback
    3. 修复错误(如果有)

    希望这对您有所帮助,如果您有任何问题,请告诉我。

    【讨论】:

    • 谢谢!我应该在上面问这个,但我也认为当我过滤不同的水果时改变线条的颜色会很好。为此,我尝试了以下操作: .attr("stroke", function(d){ if (d.Fruit == "strawberry") {return colors(d.key)} else if (d.Fruit == "grape ") {return colors1(d.key)} else {return colors2(d.key)}});
    • 很高兴我能帮上忙。好的,这将在initialGraph 内工作,但不在updateGraph 内工作,因为在这种情况下d.Fruitundefined。尝试使用 if(fruit === 'grape') { return colors1(d.key)}... (即使用 fruit 传递给 updateGraph 函数。我试过这个并且它有效(使用 3 个单独的颜色域)。此外,请确保在这种情况下更新图例。如果您遇到任何困难这样做,让我知道。我会帮你的(下班后)。
    • 好的,所以我保留了initialGraph 并更改了updateGraph。然而仍然没有运气。我将 .style(...) 添加到 var initialPath 和 var var selectFruitGroups - 这是正确的吗?你能编辑上面的sn-p吗?感谢您的宝贵时间 - 非常感谢 :)
    • 完成。查看新的 sn-p。乐于助人。
    猜你喜欢
    • 2013-12-19
    • 2012-11-13
    • 1970-01-01
    • 2012-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-08
    相关资源
    最近更新 更多