Search code examples
javascriptgraphd3.jslinegraph

Multi Line Ordinal d3 chart


Apologies if this seems like a duplicate D3 question. I've spent 2 days trying to figure out how to do this.

I'm trying to create a multi-line chart with the x-axis as an ordinal scale, and the y axis as a normal linear scale. Everything I've seen involves using time and linear scales combined and I can't seem to convert the examples to make them work how I want.

Here's my sample JSON data:

var data = 
[
    { "Supplier": "Supplier1", "Half": "2013 2H", "Value": 99.86047786 },
    { "Supplier": "Supplier1", "Half": "2013 1H", "Value": 93.86047786 },
    { "Supplier": "Supplier1", "Half": "2012 2H", "Value": 98.86047786 },
    { "Supplier": "Supplier1", "Half": "2012 1H", "Value": 96.86047786 },
    { "Supplier": "Supplier2", "Half": "2013 2H", "Value": 97.86047786 },
    { "Supplier": "Supplier2", "Half": "2013 1H",  "Value": 91.86047786 },
    { "Supplier": "Supplier2", "Half": "2012 2H","Value": 93.86047786 },
    { "Supplier": "Supplier2", "Half": "2012 1H", "Value": 94.86047786 },
    { "Supplier": "Supplier3", "Half": "2013 2H", "Value": 92.86047786 },
    { "Supplier": "Supplier3", "Half": "2013 1H", "Value": 91.86047786 },
    { "Supplier": "Supplier3", "Half": "2012 2H", "Value": 88.86047786 },
    { "Supplier": "Supplier3", "Half": "2012 1H", "Value": 87.86047786 },   
    { "Supplier": "Supplier4", "Half": "2013 2H", "Value": 88.86047786 },
    { "Supplier": "Supplier4", "Half": "2013 1H", "Value": 86.86047786 },
    { "Supplier": "Supplier4", "Half": "2012 2H", "Value": 83.86047786 },
    { "Supplier": "Supplier4", "Half": "2012 1H", "Value": 81.86047786 },   
];

And here's where I've gotten so far in trying to create the chart:

//Width and height
var margin = {top: 20, right: 20, bottom: 30, left: 40};           
var width = 600 - margin.left - margin.right;
var height= 500-margin.top -margin.bottom;
var w = width;
var h = height;

var xScale = d3.scale.ordinal()
    .domain(data.map(function (d) {return d.Half; }))
    .rangeRoundBands([margin.left, width], 0.05);

var xAxis = d3.svg.axis().scale(xScale).orient("bottom").tickSize(height - margin.bottom);

var yScale = d3.scale.linear()
    .domain([0, d3.max(data, function(d) {return d.Value; })])
    .range([h,0]);


//Create SVG element
var svg = d3.select("body")
    .append("svg")
    .attr("width", w)
    .attr("height", h);


svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + 0 + ")")
    .call(xAxis);

data = d3.nest().key(function(d) { return d.Supplier; }).entries(data); 

What I can't figure out is how to create the lines for the 4 different "suppliers" with the "Half" date buckets as the X coordinates and the "Value" as the Y coordinates. Ideally I would have 4 lines on the graph, one for each supplier, each with a different color.

Any help/direction would be greatly appreciated.


Solution

  • This should get you started: http://jsfiddle.net/hU6r6/

    Setting the domain of the xScale properly

    // Find all the unique x-axis values
    // Fortunately, alphabetical sorting of the values also yields
    // the correct order
    var xValues = d3.set(data.map(function (d) { return d.Half; })).values().sort();
    
    // Set up the domain using only the unique values for the xAxis
    var xScale = d3.scale.ordinal()
        .domain(xValues)
        // Setting the rangePoints instead of rangeBands
        .rangePoints([0, width], 0.5);
    

    Append DOM elements tied to the data

    // This is the same data as you have created
    var supplierData = d3.nest().key(function(d) { return d.Supplier; }).entries(data); 
    
    // Create a line object which will set the 'd' attributes for the paths
    var line = d3.svg.line()
                      .interpolate("linear")
                      .x(function (d) { return xScale(d.Half); })
                      .y(function (d) { return yScale(d.Value); });
    
    // To choose different colors
    var colors = d3.scale.category10();
    
    // The chart container
    var gLines = svg.selectAll('g.chart-area').data([ supplierData ]);
    
    gLines.enter()
      .append('g')
      .classed('chart-area', true)
      .attr('transform', 'translate(' + margin.left + ',' + margin.top + ")");
    
    // Our 'paths' which are the lines
    var lines = gLines.selectAll('path.supplier').data(function (d) { return d; });
    
    // Our 'paths' which are the lines
    lines.enter()
      .append('path')
      .classed('supplier', true)
      // The data is of the form { key: 'SupplierX', values: [ ... ] }
      .attr('d', function (d) { return line(d.values); })
      .attr('fill', 'none')
      .attr('stroke', function (d, i) { return colors(i); })
    

    Next steps

    If you followed the tutorial, then you will see that the jsFiddle only implements the enter phase of the data. Perhaps that is enough for your purposes and update and exit phases are not needed. Nevertheless, you should follow the tutorial completely.

    Apart from that,this graph is still bare-bone and lacks proper y-axis and labels for the colors, etc.