Skip to content Skip to sidebar Skip to footer

Dynamic Number Of Lines On Chart

I currently have a d3 multiseries line chart which displays how many emails and phone calls have been received. My data retrieval and data structure is as follows: var allCommunica

Solution 1:

Your question might be a little too broad for StackOverflow, but I'll try to help. The way I always approach the question of how should my API output data, is to ask how is my data going to be consumed on the front-end? In this case, you are trying to create a d3 multi-line chart and d3 will want an array of objects containing an array of data points (here's a great example). Something like this in JSON:

[
  {
    key:'Email', //<--identifiesthelinevalues: [ //<--pointsfortheline
      {
        xVal:'20160101',
        Value:10
      }, {
        xVal:'20160102',
        Value:20
      }, ...
    ]
  }, {
    key:'Phone',
    values: [
      {
        xVal:'Jan',
        Value:30
      }, {
        xVal:'20160102',
        Value:25
      }, ...
    ]
  },
  ...
]

Now the question becomes how to get your data into a structure like that. Given many hours, you could probably write a linq statement that'll do but, I kinda like returning a flat JSON object (after all if we are writing a re-useable restful interface, flat is the most useful). So, how then would we make that final jump for our easy to use d3 structure. Given your:

.Select(g =>new
{
   Type = g.Key.Method,
   xVal = g.Key.Month,
   Value = g.Count()
});

would produce a JSON object like:

[{"Type":"Phone","xVal":"Feb","Value":1},{"Type":"Email","xVal":"Jan","Value":3},{"Type":"Phone","xVal":"Jan","Value":1}]

d3 could then get to our "easy to work with" format as easy as:

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

Which produces:

[{"key":"Phone","values":[{"Type":"Phone","xVal":"Feb","Value":1},{"Type":"Phone","xVal":"Jan","Value":1}]},{"key":"Email","values":[{"Type":"Email","xVal":"Jan","Value":3}]}]

From this structure, your multi-line chart becomes a breeze....


EDITS FOR COMMENTS

I really didn't understand what you were attempting to do with some of your code (in particular with your methods variable - the data was already in a great format for d3). So I refactored a bit:

<!DOCTYPE html><html><head><scriptdata-require="d3@3.5.3"data-semver="3.5.3"src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script><style>body {
      font: 10px sans-serif;
    }
    
    .axis path,
    .axis line {
      fill: none;
      stroke: #000;
      shape-rendering: crispEdges;
    }
    
    .x.axis path {
      display: none;
    }
    
    .line {
      fill: none;
      stroke: steelblue;
      stroke-width: 1.5px;
    }
  </style></head><body><script>// function buildCommunicationLineChart(data, placeholder, callback, type) {var margin = {
        top: 20,
        right: 30,
        bottom: 40,
        left: 50
      },
      width = 960 - margin.left - margin.right,
      height = 500 - margin.top - margin.bottom;

    var colors = {
      "Phone": "#FF6961",
      "Email": "#779ECB"
    }
    
    var color = d3.scale.category10();

    var data = [{
      "Type": "Phone",
      "xValue": 1,
      "Value": 5
    }, {
      "Type": "Email",
      "xValue": 1,
      "Value": 7
    }, {
      "Type": "Email",
      "xValue": 2,
      "Value": 1
    }, {
      "Type": "Phone",
      "xValue": 2,
      "Value": 4
    }, {
      "Type": "Phone",
      "xValue": 4,
      "Value": 2
    }];

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

    var x;
    var type = "month";
    if (type == "month") {
      var x = d3.scale.linear()
        .domain([1, 31])
        .range([0, width]);
    } elseif (type == "year") {
      var x = d3.scale.linear()
        .domain([1, 12])
        .range([0, width]);
    }

    var y = d3.scale.linear()
      .domain([0, 100])
      .range([height, 0]);

    var xAxis = d3.svg.axis()
      .scale(x)
      .tickSize(-height)
      .tickPadding(10)
      .tickSubdivide(true)
      .orient("bottom");

    var yAxis = d3.svg.axis()
      .scale(y)
      .tickPadding(10)
      .tickSize(-width)
      .tickSubdivide(true)
      .orient("left");

    var line = d3.svg.line()
      .interpolate("linear")
      .x(function(d) {
        returnx(d.xValue);
      })
      .y(function(d) {
        returny(d.Value);
      });

    var svg = d3.select('body').append("svg")
      .attr("width", width + margin.left + margin.right + 50)
      .attr("height", height + margin.top + margin.bottom)
      .attr("class", "chart")
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
      
    y.domain([
      0,
      d3.max(nest, function(t) { return d3.max(t.values, function(v) { return v.Value; }); })
    ]);
    
    x.domain([
      d3.min(nest, function(t) { return d3.min(t.values, function(v) { return v.xValue; }); }),
      d3.max(nest, function(t) { return d3.max(t.values, function(v) { return v.xValue; }); })
    ]);
    
    nest.forEach(function(d){
      for (var i = x.domain()[0]; i <= x.domain()[1]; i++){
        if (!d.values.some(function(v){ return (v.xValue === i) })){
          d.values.splice((i - 1), 0, {xValue: i, Value: 0});
        }
      }
    });
    
    var xAxis = svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);

    if (type == "year") {
      xAxis
        .append("text")
        .attr("class", "axis-label")
        .attr("transform", "none")
        .attr("y", margin.top + 15)
        .attr("x", width / 2)
        .text('Month');
    } elseif (type == "month") {
      xAxis
        .append("text")
        .attr("class", "axis-label")
        .attr("y", margin.top + 15)
        .attr("x", width / 2)
        .text('Day')
        .style('text-anchor', 'middle');
    }

    svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
      .append("text")
      .attr("class", "axis-label")
      .attr("transform", "rotate(-90)")
      .attr("y", (-margin.left) + 15)
      .attr("x", -height / 2)
      .text('Communications')
      .style('text-anchor', 'middle');

    svg.append("clipPath")
      .attr("id", "clip")
      .append("rect")
      .attr("width", width)
      .attr("height", height);

    /*
    color.domain(d3.keys(nest[0]).filter(function(key) {
      return key === nest[0].key;
    }));

    var methods = color.domain().map(function(commType) {
      return {
        commType: commType,
        values: nest.map(function(d) {
          return {
            xValue: d.xVal,
            Value: d.Value
          };
        })
      };
    });
    */var method = svg.selectAll('.method')
      .data(nest)
      .enter().append('g')
      .attr('class', 'method');

    method.append('path')
      .attr('class', 'line')
      .attr('d', function(d) {
        returnline(d.values);
      })
      .style('stroke', function(d) {
        returncolor(d.key);
        // OR if you want to use you defined ones//return colors[d.key];
      });

    method.append('text')
      .attr("transform", function(d) {
        var len = d.values.length - 1;
        return"translate(" + x(d.values[len].xValue) + "," + y(d.values[len].Value) + ")";
      })
      .attr('x', 3)
      .attr('dy', '.35em')
      .text(function(d) {
        return d.key;
      });

    //if (callback) {//  callback();//}//  }</script></body></html>

EDIT FOR COMMENTS 2

That's actually a tricky question. How about:

// for each dataset
nest.forEach(function(d){
  // loop our domainfor (var i = x.domain()[0]; i <= x.domain()[1]; i++){
    // if there's no xValue at that locationif (!d.values.some(function(v){ return (v.xValue === i) })){
      // add a zero in place
      d.values.splice((i - 1), 0, {xValue: i, Value: 0});
    }
  }
});

Code sample above is edited also.

Post a Comment for "Dynamic Number Of Lines On Chart"