0

Prefacing this with the fact I'm new to D3 and it's probably a simple mistake I'm missing - I know it has to be. Something with the way I'm attempting to access the data.

Trying to figure this guy out for a while. (maybe I've looked at it too long)

I played around with it until my line finally showed up at all, but now it's not displaying the full line, just two points - that are numbers I'm not sure of.

Thanks in advance!

EDIT: The goal is to use lineData[0] to plot one line along the x-axis of lineData1 (months). Eventually, lineData2 will be used for a second line, but just trying to get the first line to work at the moment.

EDIT: Updated code (per Shashank's suggetions) can be found here: https://codepen.io/anon/pen/RevxYG?editors=0010

My updated graph is looking better, but still seeing NaN values: enter image description here

Here's my code:

 let lineData = [
    [4, 2, 0, 3, 3, 4, 52, 3, 0, 3, 3, 1], 
    [0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0], 
    ["Oct", "Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep"]
  ]
            console.log("lineData", lineData)

        // set dimensions of the line chart
        let width = 393,
            height = 285;


        // set the scales
        let yScale = d3.scaleLinear()    
            .domain([0, 52])
            .range([height - 40 , 0]);

        let xScale = d3.scaleOrdinal()
            .range([0, width - 40]);

        // create the line
        let line = d3.line()
            .x(function(d, i) { return xScale(i); })
            .y(function(d) { return yScale(d[0]); });

        // create svg container
        let lineChart = d3.select(element)
            .append("svg")
            .attr("viewbox", "0 60 960 600")
            .attr("preserveAspectRatio", "xMinYMin meet")
            .attr("width", width)
            .attr("height", height)
            .attr("class", "line-chart")

        lineChart.append("path")
            .datum(lineData)
            .attr("d", line)
            .attr("fill", "none")
            .attr("stroke", "rebeccapurple")
            .attr("stroke-width", 2)

        lineChart.selectAll("dot")
            .data(lineData)
            .attr("d", line)
            .enter()
            .append("circle")
            .attr("r", 3.5)
            .attr("cx", function(d, i) { return xScale(i)})
            .attr("cy", function(d) { return yScale(d[0])})
            .attr("fill", "rebeccapurple")

        lineChart.append("g")
            .attr("transform", "translate(20, 245)")
            .call(d3.axisBottom(xScale))

        lineChart.append("g")
            .attr("transform", "translate(20, 0)")
            .call(d3.axisLeft(yScale))

The data is being passed in via a function.

the data is: enter image description here

and my chart looks like: enter image description here

and my path/dots being generated look like: enter image description here

Almost_Ashleigh
  • 524
  • 1
  • 4
  • 24
  • Can you clarify exactly what is the issue? You have dates in your dataSet and you would need to have a timeScale applied to get actual X,Y coordinates – Mike Tung Oct 26 '18 at 12:56
  • Could you include your data instead of the img? – mahan Oct 26 '18 at 12:56
  • @MikeTung I thought Ordinal scales would work, no? – Almost_Ashleigh Oct 26 '18 at 12:58
  • @mahan yep, one sec. Edited it – Almost_Ashleigh Oct 26 '18 at 12:58
  • Need some clarification: 1. Are you trying to plot 1 line using `lineData[0]` (array of 12 numbers) and 12 circles using `lineData[1]` (array of 12 numbers)? 2. Or are you trying to plot 2 lines based on `lineData[0]` and `lineData[1]` and circles accordingly? (considering the months in `lineData[2]` as **xAxis** in both of the above cases). That's because you're binding `lineData` to the circles along with calling the `.attr('d', line)` for the circles which is confusing. – Shashank Oct 26 '18 at 13:18
  • @Shashank trying to plot 1 line using lineData[0] (array of 12 numbers) Eventually, using the 2nd array to plot a second line (just trying to get the first line to work) – Almost_Ashleigh Oct 26 '18 at 13:20
  • Okay. Got it. Thanks. – Shashank Oct 26 '18 at 13:21

1 Answers1

1

Missing points:

  1. Important: Ideally you should be using a d3 time scale as you're looking to plot a line based on months. But an ordinal scale would work too. I'm guessing you're looking for d3.scaleBand here.

Band scales are like ordinal scales except the output range is continuous and numeric.

Why not use d3.scaleOrdinal? Here's an example:

let lineData = [
    [4, 2, 0, 3, 3, 4, 52, 3, 0, 3, 3, 1], 
    [0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0], 
    ["Oct", "Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep"]
  ]

        let xScale = d3.scaleOrdinal().range(lineData[2])
            .domain(lineData[0]);
            
    console.log('domain is: ' + xScale.domain());
    console.log('range is: ' + xScale.range());
<script src="https://d3js.org/d3.v4.min.js"></script>

You see how the repeated values get mapped onto the months which wouldn't help in your case and so the d3.scaleBand() (you could also play around with using d3.scalePoint. Read more about it in this answer.

  1. Using the above band scale, the domain you'd set would be the lineData[2] the following way:

    xScale = d3.scaleBand().domain(lineData[2])
    
  2. Transforms weren't being applied to the line and the circles. So I appended <g> containers to contain the lines and circles respectively and transformed these group elements. For example:

    var dots = lineChart.append('g')
        .attr("transform", "translate(20, 0)");
    

The solution below just answers this specific question on appending the first line to the chart. As you mentioned in the comments that you'd eventually work on adding the second element as another line, the path selection would change

let lineData = [
    [4, 2, 0, 3, 3, 4, 52, 3, 0, 3, 3, 1], 
    [0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0], 
    ["Oct", "Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep"]
  ]
        // set dimensions of the line chart
        let width = 393,
            height = 285;


        // set the scales
        let yScale = d3.scaleLinear()    
            .domain([0, 52])
            .range([height - 40 , 0]);

        let xScale = d3.scaleBand().domain(lineData[2])
            .rangeRound([0, width - 40]);

        // create the line
        let line = d3.line()
            .x(function(d, i) { 
             return xScale(lineData[2][i]) + xScale.bandwidth()/2; 
            })
            .y(function(d) { return yScale(d); });

        // create svg container
        let lineChart = d3.select('body')
            .append("svg")
            .attr("viewbox", "0 60 960 600")
            .attr("preserveAspectRatio", "xMinYMin meet")
            .attr("width", width)
            .attr("height", height)
            .attr("class", "line-chart")

    var lineG = lineChart.append('g')
         .attr("transform", "translate(20, 0)");
          
        lineG.append("path")
            .data(lineData)
            .attr("d", line)
            .attr("fill", "none")
            .attr("stroke", "rebeccapurple")
            .attr("stroke-width", 2)

   var dots = lineChart.append('g')
         .attr("transform", "translate(20, 0)");
        dots.selectAll("dot")
            .data(lineData[0])
            .enter()
            .append("circle").classed('dot', true)
            .attr("r", 3.5)
            .attr("cx", function(d, i) { return xScale(lineData[2][i]) + xScale.bandwidth()/2})
            .attr("cy", function(d) { return yScale(d)})
            .attr("fill", "rebeccapurple")

        lineChart.append("g")
            .attr("transform", "translate(20, 245)")
            .call(d3.axisBottom(xScale))

        lineChart.append("g")
            .attr("transform", "translate(20, 0)")
            .call(d3.axisLeft(yScale))
<script src="https://d3js.org/d3.v4.min.js"></script>

Suggestion: It's not a good practice to set a domain to a static value as you do for yScale [0,52]. Try using d3.extent or d3.min methods.

Hope this helps.

Edit: As you're trying to use d3.scalePoint, you're making a minor mistake in choosing the xScale value in the line function and for the circles. You'd need to choose categories[i] in the line fn as following:

.x(function(d, i) { 
     return xScale(categories[i]); 
 })

Updated snippet using d3.scalePoint

let data = {
    'New': [4, 2, 0, 3, 3, 4, 52, 3, 0, 3, 3, 1], 
    'Terminated': [0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0]
  };
  let categories = ["Oct", "Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep"];
  
            let lineData = [data.New, data.Terminated, categories];

        
            // set dimensions of the line chart
            let width = 393,
                height = 285;

            // set the scales
            let yScale = d3.scaleLinear()    
                .domain([0, 60]).nice()
                .range([height - 40 , 0]);
        
            let xScale = d3.scalePoint()
                .domain(lineData[2].map(function(d, i) { return d; }))
                .range([0, width - 40]);
        
            // create the line
            let line = d3.line()
                .x(function(d, i) { 
                 return xScale(categories[i]); 
                })
                .y(function(d) { return yScale(d); });
        
            // create svg container
            let lineChart = d3.select('body')
                .append("svg")
                .attr("viewbox", "0 60 960 600")
                .attr("preserveAspectRatio", "xMinYMin meet")
                .attr("width", width)
                .attr("height", height)
                .attr("class", "line-chart")

    var lineG = lineChart.append('g')
         .attr("transform", "translate(20, 0)");
            lineG.append("path")
                .datum(lineData)
                .attr("d", function(d) { return line(d[0]); })
                .attr("fill", "none")
                .attr("stroke", "rebeccapurple")
                .attr("stroke-width", 2)
          
            lineChart.selectAll("dot")
                .data(lineData[0])
                .enter()
                .append("circle")
                .attr("transform", "translate(20, 0)")
                .attr("r", 3.5)
                .attr("cx", function(d, i) { return xScale(categories[i])})
                .attr("cy", function(d) { return yScale(d)})
                .attr("fill", "rebeccapurple")
        
            lineChart.append("g")
                .attr("transform", "translate(20, 245)")
                .call(d3.axisBottom(xScale))

        
            lineChart.append("g")
                .attr("transform", "translate(20, 0)")
                .call(d3.axisLeft(yScale))
<script src="https://d3js.org/d3.v4.min.js"></script>

Hope this helps too.

Shashank
  • 5,570
  • 1
  • 11
  • 17
  • This is very helpful! A couple things - Time scale returned some really weird results. I wanted to stick with ordinal because it's a string; however; I found scalePoint did exactly what I needed! I tried making some of the changes, but it broke the graph. With some of the changes I made, now the graph looks better, but I'm still seeing NaN values for some of the data. You can see my updated code here: https://codepen.io/anon/pen/RevxYG?editors=0010. I'll add this link and the updated image to my OP – Almost_Ashleigh Oct 26 '18 at 15:06
  • 1
    this is actually perfect, I went your route instead of scalePoint. Thanks again! – Almost_Ashleigh Oct 26 '18 at 15:25
  • @Almost_Ashleigh Updated the post with an implementation using `d3.scalePoint` (if you're still interested in knowing how that'd work out). Glad I could help! :) – Shashank Oct 26 '18 at 15:29