Search code examples
d3.jscolorsscale

Add color to multiple paths from an array of exact same data in D3.js


The question is probably misleading but I didn't know how to say it more precisely. Basically, my data goes as follow:

    {
        "pitcher": 547943,
        "pitch_type": "CH",
        "velo": 80.15329032258065,
        "hmov": 0,
        "vmov": 0,
        "name": "hyun-jin-ryu"
      },
    {
      "pitcher": 547943,
      "pitch_type": "CH",
      "velo": 80.15329032258065,
      "hmov": 12.729861677419354,
      "vmov": 5.4084,
      "name": "hyun-jin-ryu"
    },
    {
        "pitcher": 547943,
        "pitch_type": "CU",
        "velo": 72.77105263157895,
        "hmov": 0,
        "vmov": 0,
        "name": "hyun-jin-ryu"
      },
    {
      "pitcher": 547943,
      "pitch_type": "CU",
      "velo": 72.77105263157895,
      "hmov": -13.357961403508773,
      "vmov": -13.062238596491229,
      "name": "hyun-jin-ryu"
    }

I want to get a path for each pitch_type, starting from (hmov[0],vmov[0]) or 0,0 and going to (hmov[1], vmov[1]). I also created a color scale associated to "velo" but can't find a way to assign it to my path stroke. I suspect it has to do with having 2 values of velo, but cant really say if that is the problem for sure.

    //Loop through each pitch    
    dataNest.forEach(function(d) { 
        svg.append("path")
            .data([data])
            .attr("d", pitchLine(d.values))
            .attr("stroke", function(d) { return veloScale(d); }) //Problematic part
            .attr("stroke-witdh", 2);
    });

Full code:

const margin = {top: 25, bottom: 25, right: 25, left: 25};
const height = 300 - margin.top - margin.bottom;
const width = 300 - margin.left - margin.right;

//Set Ranges
let x = d3.scaleLinear().range([0, width]);
let y = d3.scaleLinear().range([height, 0]);
let veloScale = d3.scaleSequential(d3.interpolateViridis);


//Set line generator
let pitchLine = d3.line()
            .x(function(d) { return x(d.hmov); })
            .y(function(d) { return y(d.vmov); });

//Add SVG canvas
let svg = d3.select("body")
    .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
    .append("g")
        .attr("transform", 
              "translate(" + margin.left + "," + margin.top + ")");

///////////////////////////////

//Get the Data
d3.json("ryu.json").then(function(data) {
    data.forEach(function(d) {
        d.hmov = +d.hmov;
        d.vmov = +d.vmov;
        d.velo = +d.velo;
    });

    //Scales
    x.domain(d3.extent(data, function(d) { return d.hmov; }));
    y.domain(d3.extent(data, function(d) { return d.vmov; }));
    veloScale.domain(d3.extent(data, function(d) { return d.velo; }))

    //Nesting data
    let dataNest = d3.nest()
        .key(function(d) { return d.pitch_type; })
        .entries(data);

    //Loop through each pitch    
    dataNest.forEach(function(d) { 
        svg.append("path")
            .data([data])
            .attr("d", pitchLine(d.values))
            .attr("stroke", function(d) { return veloScale(d); })
            .attr("stroke-witdh", 2);
    });

Solution

  • The section of your code where you loop through the dataNest array should be updated so that

    a) the data is joined at the correct level, ie the dataNest array to create each path, and the d.values for the datum of the path.

    b) the stroke colour function passes in the value from the array you want to map to a colour. As you may have different velo values for each element in the d.values array, you will need to decide which one you will use. The example below uses the velo value from the first element in the array.

    //Create a separate g element to contain the path, based on the nested array    
    let pitch = svg.selectAll(".pitch-type")
                .data(dataNest)
                .enter()
                .append("g")
         //for each g element, add a path and assign the d.values for that path
            pitch.append("path")
              .datum(d => d.values)
              .attr("d", d => pitchLine(d))
    //pass in the velo value to get a colour
              .attr("stroke", d => veloScale(d[0].velo))
              .attr("stroke-witdh", 2);
    

    let data = [{
            "pitcher": 547943,
            "pitch_type": "CH",
            "velo": 80.15329032258065,
            "hmov": 0,
            "vmov": 0,
            "name": "hyun-jin-ryu"
          },
        {
          "pitcher": 547943,
          "pitch_type": "CH",
          "velo": 80.15329032258065,
          "hmov": 12.729861677419354,
          "vmov": 5.4084,
          "name": "hyun-jin-ryu"
        },
        {
            "pitcher": 547943,
            "pitch_type": "CU",
            "velo": 72.77105263157895,
            "hmov": 0,
            "vmov": 0,
            "name": "hyun-jin-ryu"
          },
        {
          "pitcher": 547943,
          "pitch_type": "CU",
          "velo": 72.77105263157895,
          "hmov": -13.357961403508773,
          "vmov": -13.062238596491229,
          "name": "hyun-jin-ryu"
        }]
        
        const margin = {top: 25, bottom: 25, right: 25, left: 25};
        const height = 300 - margin.top - margin.bottom;
        const width = 300 - margin.left - margin.right;
    
    //Set Ranges
        let x = d3.scaleLinear().range([0, width]);
        let y = d3.scaleLinear().range([height, 0]);
        let veloScale = d3.scaleSequential(d3.interpolateViridis);
    
    
    //Set line generator
    let pitchLine = d3.line()
                .x(function(d) { return x(d.hmov); })
                .y(function(d) { return y(d.vmov); });
    
    //Add SVG canvas
    let svg = d3.select("body")
        .append("svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
        .append("g")
            .attr("transform", 
                  "translate(" + margin.left + "," + margin.top + ")");
    
    
        data.forEach(function(d) {
            d.hmov = +d.hmov;
            d.vmov = +d.vmov;
            d.velo = +d.velo;
        });
    
        //Scales
        x.domain(d3.extent(data, function(d) { return d.hmov; }));
        y.domain(d3.extent(data, function(d) { return d.vmov; }));
        veloScale.domain(d3.extent(data, function(d) { return d.velo; }))
    
        //Nesting data
        let dataNest = d3.nest()
            .key(function(d) { return d.pitch_type; })
            .entries(data);
        
       
        let pitch = svg.selectAll(".pitch-type")
        	.data(dataNest)
        	.enter()
        	.append("g")
     
        pitch.append("path")
          .datum(d => d.values)
          .attr("d", d => pitchLine(d))
          .attr("stroke", d => veloScale(d[0].velo))
          .attr("stroke-witdh", 2);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>