cynichole
cynichole

Reputation: 53

D3 v4 url(#gradient) not returning the fill color

So I've done this color-coded grid in d3.js. There are some squares that should be half one color and half another. I've laid out the gradients according to other examples. But when I call the url(#gridfill7) it doesn't return the gradient - squares that use the gradients are empty.

Looking at other questions, it might be a browser bug, but I can't be certain.

Here is a snippet demonstrating the problem:

var gridvis = null;


function highlightGrid() {
  var gridData = [];

var squareSize = 30;
var squarePad =5;
var numPerRow = 9;
var margin = {top: 10, right: 30, bottom: 30, left: 60};
var width = 750 - margin.left - margin.right;
var height = 520 - margin.top - margin.bottom;


var d = d3.csvParse(d3.select("pre").remove().text());

 for (var i = 0; i < d.length; i++) {
    //  console.log(d[i].report_num);
    //  console.log(d[i].platform_medium);
      d[i].report_num = +d[i].report_num;
      d[i].platform_medium = +d[i].platform_medium;
      
      d[i].col = i % numPerRow;
      d[i].x = d[i].col * (squareSize + squarePad);
      d[i].row = Math.floor(i/numPerRow);
      d[i].y = d[i].row * (squareSize + squarePad);
      gridData.push(d[i]);
  }


  let platformData = gridData;
    
  //console.log(gridData);

  var gridvis = d3.select("#chart2")
                  .append("svg")
                  .attr("width", width)
                  .attr("height", height)
                  .append("g")
                  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  

  // square grid
  // @v4 Using .merge here to ensure
  // new and old data have same attrs applied
  //console.log(platformData);
    
  var squares = gridvis.selectAll('.square').data(platformData, function (d) {  return d.report_num; });


  var gridFill7 = gridvis
                         .append("linearGradient")
                         .attr("id", "gridFill7")
                         .attr("x1", "0%")
                        .attr("x2", "0%")
                        .attr("y1", "0%")
                        .attr("y2", "100%")//since its a vertical linear gradient 
                        .attr("gradientUnits", "userSpaceOnUse");

  gridFill7.append("stop")
           .style("offset", "50%")
           .style("stop-color", "#0652DD")
           .style("stop-opacity", 1);

  gridFill7.append("stop")
           .style("offset", "100%")
           .style("stop-color", "#C4E538")
           .style("stop-opacity", 1);

  var gridFill8 = gridvis
                         .append("linearGradient")
                         .attr("id", "gridFill8")
                         .attr("x1", "0%")
                        .attr("x2", "0%")
                        .attr("y1", "0%")
                        .attr("y2", "100%")//since its a vertical linear gradient 
                        .attr("gradientUnits", "userSpaceOnUse");

  gridFill8.append("stop")
           .style("offset", "50%")
           .style("stop-color", "#0652DD")
           .style("stop-opacity", 1);

  gridFill8.append("stop")
           .style("offset", "100%")
           .style("stop-color", "#EE5A24")
           .style("stop-opacity", 1);

  var squaresE = squares.enter()
                        .append('rect')
                        .classed('square', true);

  var squares = squares.merge(squaresE)
                       .attr('width', squareSize)
                       .attr('height', squareSize)
                       .attr('fill', "#fff")
                       .classed('fill-square', function (d) { return d.platform_medium; })
                       .attr('x', function (d) { return d.x;})
                       .attr('y', function (d) { return d.y;})
                       .attr('opacity', 1);

                       gridvis.selectAll('.fill-square')
                       .transition()
                       .duration(800)
                       .attr('opacity', 1.0)
                       .attr('fill', function (d) { 
                                                       if (d.platform_medium===1) {return "#0652DD";}
                                                       else if (d.platform_medium===2) {return "#9980FA";}
                                                       else if (d.platform_medium===3) {return "#C4E538";}
                                                       else if (d.platform_medium===4) {return "#ED4C67";}
                                                       else if (d.platform_medium===5) {return "#F79F1F";}
                                                       else if (d.platform_medium===6) {return "#EE5A24";}
                                                       else if (d.platform_medium===7) {return "url(#gridfill7)";}
                                                       else if (d.platform_medium===8) {return "url(#gridfill8)";}

                                                   });
                  }
              
   highlightGrid();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="chart2"></div>
<pre>report_num,covid_related,platform_medium
1,0,1
2,0,1
3,0,1
4,0,1
5,0,1
6,0,3
7,0,1
8,0,3
9,0,1
10,0,3
11,0,1
12,0,3
13,1,2
14,1,1
15,1,3
16,1,1
17,1,1
18,1,1
19,1,1
20,1,1
21,1,1
22,1,1
23,1,1
24,0,1
25,0,7
26,1,1
27,1,1
28,1,1
29,1,1
30,1,1
31,1,1
32,1,1
33,1,1
34,1,1
35,1,1
36,0,1
37,0,1
38,0,2
39,0,7
40,1,1
41,1,1
42,1,1
43,1,1
44,1,1
45,1,1
46,0,5
47,0,5
48,0,1
49,0,1
50,0,1
51,0,1
52,1,1
53,1,1
54,1,1
55,0,1
56,0,5
57,0,1
58,0,1
59,0,1
60,0,1
61,0,7
62,0,1
63,0,1
64,0,1
65,0,1
66,0,1
67,1,5
68,1,1
69,1,1
70,1,3
71,1,3
72,1,1
73,1,1
74,1,7
75,1,8
76,1,1
77,1,4
78,1,2</pre>

Upvotes: 1

Views: 279

Answers (1)

Andrew Reid
Andrew Reid

Reputation: 38171

If we inspect your gradient we see something odd:

enter image description here

There is no offset value for the stops but you have set one. The problem is the stop offset value is an attribute not something that cannot be used as a css property, unlike stop-opacity and stop-color. selection.style sets css properties, selection.attr sets attributes. So we need to update your code to use selection.attr as follows:

  gridFill7.append("stop")
       .attr("offset", "50%")  // also for the other gridFill7 stop and gridFill8 of course
       ...

Now we have a gradient as follows, with an offset attribute:

enter image description here

We still have an issue in the gradient - we don't want to set the gradient units to userSpaceOnUse - in this coordinate space "Percentages represent values relative to the current SVG viewport." (MDN). We can use the default objectBoundingBox, where "Percentages represent values relative to the bounding box for the object." This sounds more appropriate. As this is the default option, let's just remove where we set the gradient units altogether.

The next issue is a typo:

  else if (d.platform_medium===7) {return "url(#gridfill7)";}
  else if (d.platform_medium===8) {return "url(#gridfill8)";}

Your gradient ids use capitals for the f in Fill, but you are looking for lower case fs when assigning fills.

Once corrected, we should see the end result desired:

enter image description here

However, the transition is still glitchy. D3 transitions move from a starting value to an end value. D3 is fairly clever in that it recognizes strings representing color names and treats them as colors, D3 then can interpolate between a starting color and an end colors, this is relatively straight forward.

But you have a string representing a gradient url and another representing a color - D3's interpolator doesn't know how to move from a color (#fff) to a string (url(#gridFill7)) smoothly. What value would occur at halfway? Would D3 create new gradients for the transition? As is, the transition just uses the end value throughout its duration.

A solution would be to start your opacity at 0 and transition to opacity 1 with the fills specified when first creating the elements. D3 will quite happily interpolate between 0 and 1:

var gridvis = null;


function highlightGrid() {
  var gridData = [];

var squareSize = 30;
var squarePad =5;
var numPerRow = 9;
var margin = {top: 10, right: 30, bottom: 30, left: 60};
var width = 750 - margin.left - margin.right;
var height = 520 - margin.top - margin.bottom;


var d = d3.csvParse(d3.select("pre").remove().text());

 for (var i = 0; i < d.length; i++) {
    //  console.log(d[i].report_num);
    //  console.log(d[i].platform_medium);
      d[i].report_num = +d[i].report_num;
      d[i].platform_medium = +d[i].platform_medium;
      
      d[i].col = i % numPerRow;
      d[i].x = d[i].col * (squareSize + squarePad);
      d[i].row = Math.floor(i/numPerRow);
      d[i].y = d[i].row * (squareSize + squarePad);
      gridData.push(d[i]);
  }


  let platformData = gridData;
    
  //console.log(gridData);

  var gridvis = d3.select("#chart2")
                  .append("svg")
                  .attr("width", width)
                  .attr("height", height)
                  .append("g")
                  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  

  // square grid
  // @v4 Using .merge here to ensure
  // new and old data have same attrs applied
  //console.log(platformData);
    
  var squares = gridvis.selectAll('.square').data(platformData, function (d) {  return d.report_num; });

 var gridFill7 = gridvis
                         .append("linearGradient")
                         .attr("id", "gridFill7")
                         .attr("x1", "0%")
                        .attr("x2", "0%")
                        .attr("y1", "0%")
                        .attr("y2", "100%")//since its a vertical linear gradient 
                       // .attr("gradientUnits", "userSpaceOnUse");

  gridFill7.append("stop")
           .attr("offset", "50%")
           .style("stop-color", "#0652DD")
           .style("stop-opacity", 1);

  gridFill7.append("stop")
           .attr("offset", "100%")
           .style("stop-color", "#C4E538")
           .style("stop-opacity", 1);

  var gridFill8 = gridvis
                         .append("linearGradient")
                         .attr("id", "gridFill8")
                         .attr("x1", "0%")
                        .attr("x2", "0%")
                        .attr("y1", "0%")
                        .attr("y2", "100%")//since its a vertical linear gradient 
                       // .attr("gradientUnits", "userSpaceOnUse");

  gridFill8.append("stop")
           .attr("offset", "50%")
           .style("stop-color", "#0652DD")
           .style("stop-opacity", 1);

  gridFill8.append("stop")
           .attr("offset", "100%")
           .style("stop-color", "#EE5A24")
           .style("stop-opacity", 1);

  var squaresE = squares.enter()
                        .append('rect')
                        .classed('square', true);

  var squares = squares.merge(squaresE)
                       .attr('width', squareSize)
                       .attr('height', squareSize)
                       .attr('fill', function (d) { 
                                                       if (d.platform_medium===1) {return "#0652DD";}
                                                       else if (d.platform_medium===2) {return "#9980FA";}
                                                       else if (d.platform_medium===3) {return "#C4E538";}
                                                       else if (d.platform_medium===4) {return "#ED4C67";}
                                                       else if (d.platform_medium===5) {return "#F79F1F";}
                                                       else if (d.platform_medium===6) {return "#EE5A24";}
                                                       else if (d.platform_medium===7) {return "url(#gridFill7)";}
                                                       else if (d.platform_medium===8) {return "url(#gridFill8)";}
                                   
                        })
                       .classed('fill-square', function (d) { return d.platform_medium; })
                       .attr('x', function (d) { return d.x;})
                       .attr('y', function (d) { return d.y;})
                       .attr('opacity', 0);

                       gridvis.selectAll('.fill-square')
                       .transition()
                       .duration(800)
                       .attr('opacity', 1.0)
                      

                  }
              
   highlightGrid();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="chart2"></div>
<pre>report_num,covid_related,platform_medium
1,0,1
2,0,1
3,0,1
4,0,1
5,0,1
6,0,3
7,0,1
8,0,3
9,0,1
10,0,3
11,0,1
12,0,3
13,1,2
14,1,1
15,1,3
16,1,1
17,1,1
18,1,1
19,1,1
20,1,1
21,1,1
22,1,1
23,1,1
24,0,1
25,0,7
26,1,1
27,1,1
28,1,1
29,1,1
30,1,1
31,1,1
32,1,1
33,1,1
34,1,1
35,1,1
36,0,1
37,0,1
38,0,2
39,0,7
40,1,1
41,1,1
42,1,1
43,1,1
44,1,1
45,1,1
46,0,5
47,0,5
48,0,1
49,0,1
50,0,1
51,0,1
52,1,1
53,1,1
54,1,1
55,0,1
56,0,5
57,0,1
58,0,1
59,0,1
60,0,1
61,0,7
62,0,1
63,0,1
64,0,1
65,0,1
66,0,1
67,1,5
68,1,1
69,1,1
70,1,3
71,1,3
72,1,1
73,1,1
74,1,7
75,1,8
76,1,1
77,1,4
78,1,2</pre>

Upvotes: 1

Related Questions