Search code examples
jsonsvgd3.jsradial-gradientssunburst-diagram

Problems making radial gradient in arcs using javascript, d3 with data from json file


I'm using javascript and d3 (v6) and working towards making a sunburst type plot or at least a plot with arcs at different radial distances where each arc has potentially its own gradient (a radial gradient from center to periphery). At this point I've followed some examples (e.g. here, here) where I set up a set of radialGradient elements and then use the ids from that to generate the fill in my actual arcs. But I am generating my arcs and gradients from data from a json file, and this makes it different from previous questions because the svg element generation is within the d3.json call (e.g. here). I have two problems. (1) The gradient setups have lines like:

grads.append("stop")
        .attr("offset", "0%").style("stop-color", "white")
        .attr("offset", "100%").style("stop-color", "green");

but these don't seem to actually get added to the grads elements (when I look at the inspector in the webpage).
(2) The link to the arcs doesn't seem to work, although perhaps that is because of the first problem.

JAVASCRIPT (can be found here)

var width = window.innerWidth,
    height = window.innerHeight;

var body = d3.select('body')

// append svg to the DIV
chart = d3.select(".chart");

const svg = chart.append("svg:svg")
    .attr("width", width)
    .attr("height", height);

///////////////////////////////////////  Global variables controlling the arc appearance //////////////////
const arcMin = 30;
const arcWidth = 45.5;
const arcPad = 1;
///////////////////////////////////////////////////////////////////////////////////////////////

d3.json('dataExample.json')
    .then(function (data) {

        const grads = svg.append("defs").selectAll("radialGradient").data(data.sequences);
        grads.enter().append("radialGradient")
            .attr("gradientUnits", "objectBoundingBox")
            .attr("cx", 0)
            .attr("cy", 0)
            .attr("fr", (d, i) => arcMin + (d.pulse-1) * (arcWidth))
            .attr("r", (d, i) => arcMin + d.pulse * (arcWidth))
            .attr("id", function (d) {
                return "grad" + d.code;
            });
        grads.append("stop")
            .attr("offset", "0%").style("stop-color", "white")
            .attr("offset", "100%").style("stop-color", "green");//eventually this gradient will go between two colors that are functions of the data that is read in from the json file

        console.log(grads);

        var arc = svg.selectAll('path.arc-path')
            .data(data.sequences);
        arc.enter()
            .append('svg:path')
            .attr('d', d3.arc()
                .innerRadius((d, i) => arcMin + (d.pulse - 1) * (arcWidth) + arcPad)
                .outerRadius((d, i) => arcMin + d.pulse * (arcWidth))
                .startAngle(function (d, i) {
                    ang = (i * 30) * Math.PI / 180;
                    return ang;
                })
                .endAngle(function (d, i) {
                    ang = ((i + 1) * 30) * Math.PI / 180;
                    return ang;
                })
            )
            .attr("class", ".arc-path") // assigns a class for easier selecting
            .attr("transform", "translate(600,300)") 
            //.style('fill',(d) => `rgb(${d.code * 10},${d.code*20},${255 -d.code * 7})`); this works - but doesn't use the gradients
            .style("fill", function (d) {return "url(#grad" + d.code + ")";})


    })

JSONFILE (called dataExample.json above) can be found here

{"type":"sequenceData","sequences":[{"pulse":1,"code":0},{"pulse":1,"code":1},{"pulse":1,"code":2},{"pulse":2,"code":3},{"pulse":2,"code":4},{"pulse":2,"code":5},{"pulse":2,"code":6},{"pulse":2,"code":7},{"pulse":2,"code":8},{"pulse":2,"code":9},{"pulse":2,"code":10},{"pulse":3,"code":12}]}

index.html (can be found here)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>Gradient arc test</title>
    </style>
    <script type="text/javascript" src="https://d3js.org/d3.v6.min.js"></script>
  </head>
  <body>
    <div class="chart"></div>
        <script src="arcExample.js">    </script>
  </body>
</html>

And if there are other problems or bad coding practices you notice, please let me know since I'm new to this. Thanks.

(edited)

Based on Ruben Helsloot's comment I made some jsfiddles with the various parts. There are 4 fiddles. They differ in 2 aspects: (1) how the json file is read in and (2) how the fill is generated. I'll note that for my final purpose I need to be able to read the json file from a file - either local or from a URL
(1) jsfiddle Version 1 Attempts to read json from a URL and attempts to use gradients for the fill. I think there is a problem reading from the url as well as a problem with the gradient filling.
(2) jsfiddle Version 2 Attempts to read json from a URL but does not use gradients for the fill. This has problems with reading the URL. When I do this on my local machine reading the json from a local file, this version does generate an output (although not gradients).
(3) jsfiddle Version 3 This puts the json in a local variable called data and then uses that. It also tries to use the gradients for filling.
(4) jsfiddle Version 4 This puts the json in a local variable called data and then uses that. It does not use gradients for filling. This is the only one of the 4 that gives the output on jsfiddle.


Solution

  • You should learn basic SVG 1.1 specification first and then how to add elements (not attributes) to the correct SVG structure.

    Below is fixed code though I don't know exactly what gradient do you want to achieve. You can try to change the gradientUnits attribute back to objectBoundingBox but first experiment with the r attribute as well.

    var width = window.innerWidth,
        height = window.innerHeight;
    
    var body = d3.select('body')
    
    // append svg to the DIV
    chart = d3.select(".chart");
    
    const svg = chart.append("svg:svg")
        .attr("width", width)
        .attr("height", height)
        .attr("xmlns", "http://www.w3.org/2000/svg")
        .attr("xmlns:xlink", "http://www.w3.org/1999/xlink");
    
    ///////////////////////////////////////  Global variables controlling the arc appearance //////////////////
    const arcMin = 30;
    const arcWidth = 45.5;
    const arcPad = 1;
    ///////////////////////////////////////////////////////////////////////////////////////////////
    
    var data = {"type":"sequenceData","sequences":[{"pulse":1,"code":0},{"pulse":1,"code":1},{"pulse":1,"code":2},{"pulse":2,"code":3},{"pulse":2,"code":4},{"pulse":2,"code":5},{"pulse":2,"code":6},{"pulse":2,"code":7},{"pulse":2,"code":8},{"pulse":2,"code":9},{"pulse":2,"code":10},{"pulse":3,"code":12}]};
    
            const grads = svg.append("defs").selectAll("radialGradient").data(data.sequences);
            gradsWrap = grads.enter().append("radialGradient")
                //.attr("gradientUnits", "objectBoundingBox")
                .attr("gradientUnits", "userSpaceOnUse")
                .attr("cx", 0)
                .attr("cy", 0)
                //.attr("fr", (d, i) => arcMin + (d.pulse-1) * (arcWidth))
                //.attr("r", (d, i) => arcMin + (d.pulse-1) * (arcWidth))
                .attr("fr", "0%")
                .attr("r", "25%")
                .attr("id", function (d) {
                    return "grad" + d.code;
                })
            gradsWrap.append("stop")
                .attr("offset", "0%")
                .attr("stop-color", "white");
            gradsWrap.append("stop")
                .attr("offset", "100%").attr("stop-color", "green");//eventually this gradient will go between two colors that are functions of the data that is read in from the json file
    
            console.log(grads);
    
            var arc = svg.selectAll('path.arc-path')
                .data(data.sequences);
            arc.enter()
                .append('svg:path')
                .attr('d', d3.arc()
                    .innerRadius((d, i) => arcMin + (d.pulse - 1) * (arcWidth) + arcPad)
                    .outerRadius((d, i) => arcMin + d.pulse * (arcWidth))
                    .startAngle(function (d, i) {
                        ang = (i * 30) * Math.PI / 180;
                        return ang;
                    })
                    .endAngle(function (d, i) {
                        ang = ((i + 1) * 30) * Math.PI / 180;
                        return ang;
                    })
                )
                .attr("class", ".arc-path") // assigns a class for easier selecting
                .attr("transform", "translate(" + width/2 + "," + height/2 + ")")
                .attr('stroke', 'white')
                .style('stroke-width', '2px')
                .attr("fill", function (d) {
                  //return "red";
                  return "url(#grad" + d.code + ")";
                })
    <head>
      <meta charset="utf-8" />
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
      <title>Gradient arc test</title>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    </head>
    
    <body>
      <div class="chart"></div>
    </body>