Dynamic Number Of Lines On Chart
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"