我目前有一个d3多重系列折线图,显示已收到多少封电子邮件和电话。
我的数据检索和数据结构如下:
var allCommunications = _uow.CommunicationRepository.Get()
.Where(c => c.DateOpened.Year == year)
.GroupBy(c => new { c.Method, c.DateOpened.Month })
.Select(g => new
{
Type = g.Key.Method,
xVal = g.Key.Month,
Value = g.Count()
});
然后将其转换为以下结构:
public class LineChartData
{
public int xValue { get; set; }
public int EmailValue { get; set; }
public int PhoneValue { get; set; }
}
使用以下javascript创建图表:
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,
emailLineColour = "#779ECB", phoneLineColour = "#FF6961", tooltipTextColour = "white";
var x;
if (type == "month") {
var x = d3.scale.linear()
.domain([1, 31])
.range([0, width]);
} else if (type == "year")
{
var x = d3.scale.linear()
.domain([1, 12])
.range([0, width]);
}
var minPhone = Math.min.apply(Math, data.map(function (o) { return o.PhoneValue }));
var maxPhone = Math.max.apply(Math, data.map(function (o) { return o.PhoneValue }));
var minEmail = Math.min.apply(Math, data.map(function (o) { return o.EmailValue }));
var maxEmail = Math.max.apply(Math, data.map(function (o) { return o.EmailValue }));
var minY = Math.min(minPhone, minEmail);
var maxY = Math.max(maxPhone, maxEmail);
var y = d3.scale.linear()
.domain([minY, maxY + 5])
.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");
if (type == "month") {
var emailTip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return "<strong>Emails:</strong> <span style='color:"+tooltipTextColour+"'>" + d.EmailValue + "</span><br /><strong>Day of Month:</strong><span style='color:white'>" + d.xValue + "</span>";
});
var phoneTip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return "<strong>Calls:</strong> <span style='color:" + tooltipTextColour + "'>" + d.PhoneValue + "</span><br /><strong>Day of Month:</strong><span style='color:white'>" + d.xValue + "</span>";
});
}
else if (type == "year") {
var emailTip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return "<strong>Emails:</strong> <span style='color:" + tooltipTextColour + "'>" + d.EmailValue + "</span><br /><strong>Month of Year:</strong><span style='color:white'>" + d.xValue + "</span>";
});
var phoneTip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return "<strong>Calls:</strong> <span style='color:" + tooltipTextColour + "'>" + d.PhoneValue + "</span><br /><strong>Month of Year:</strong><span style='color:white'>" + d.xValue + "</span>";
});
}
var svg = placeholder.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 + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.call(emailTip);
svg.call(phoneTip);
if (type == "year") {
svg.append("g")
.attr("class", "x axis")
.append("text")
.attr("class", "axis-label")
.attr("transform", "none")
.attr("y", (-margin.left) + 530)
.attr("x", -height + 860)
.text('Month');
}
else if (type == "month") {
svg.append("g")
.attr("class", "x axis")
.append("text")
.attr("class", "axis-label")
.attr("transform", "none")
.attr("y", (-margin.left) + 525)
.attr("x", -height + 860)
.text('Day');
}
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "y axis")
.append("text")
.attr("class", "axis-label")
.attr("transform", "rotate(-90)")
.attr("y", (-margin.left) + 15)
.attr("x", -height / 2)
.text('Communications');
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var emailLine = d3.svg.line()
.interpolate("linear")
.x(function (d) { return x(d.xValue); })
.y(function (d) { return y(d.EmailValue); });
var phoneLine = d3.svg.line()
.interpolate("linear")
.x(function (d) { return x(d.xValue); })
.y(function (d) { return y(d.PhoneValue); });
svg.selectAll('.emailLine')
.data(data)
.enter()
.append("path")
.attr("class", "line")
.attr('stroke', emailLineColour)
.attr("d", emailLine(data));
svg.selectAll("circle.emailLine")
.data(data)
.enter().append("svg:circle")
.attr("class", "emailLine")
.style("fill", emailLineColour)
.attr("cx", emailLine.x())
.attr("cy", emailLine.y())
.attr("r", 5)
.on('mouseover', emailTip.show)
.on('mouseout', emailTip.hide);
svg.selectAll('.phoneLine')
.data(data)
.enter()
.append("path")
.attr("class", "line")
.attr('stroke', phoneLineColour)
.attr("d", phoneLine(data));
svg.selectAll("circle.phoneLine")
.data(data)
.enter().append("svg:circle")
.attr("class", "phoneLine")
.style("fill", phoneLineColour)
.attr("cx", phoneLine.x())
.attr("cy", phoneLine.y())
.attr("r", 5)
.on('mouseover', phoneTip.show)
.on('mouseout', phoneTip.hide);
svg.append("text")
.attr("transform", "translate(" + (x(data[data.length - 1].xValue) + 5) + "," + y(data[data.length - 1].EmailValue) + ")")
.attr("dy", ".35em")
.style("fill", emailLineColour)
.text("Email");
svg.append("text")
.attr("transform", "translate(" + (x(data[data.length - 1].xValue) + 5) + "," + y(data[data.length - 1].PhoneValue) + ")")
.attr("dy", ".35em")
.style("fill", phoneLineColour)
.text("Phone");
if (callback) {
callback();
}
}
显然,这是非常长且非常有限的,因为图表的每个系列都是硬编码的。因此,如果添加另一种通信方法,那将是相当多的工作。解决这个问题背后的想法是拥有一个动态数量的系列,并为每个系列创建一条线。因此,我想我的数据结构必须是:
public class LineChartData
{
public string Type {get;set;} //for the label
public Data Data{get;set;}
}
public class Data
{
public int xValue { get; set; }
public int Value { get; set; }
}
或类似的东西?
所以我想我的问题是,这是构建我的数据的正确方法,更改我的查询以执行此操作的任何建议,以及如何编辑我的javascript以解决此问题。
为冗长的问题道歉,并提前感谢任何帮助。
如果需要更多信息,请询问,我会尽我所能。
谢谢,
修改
以下是Mark尝试以下建议后的更新代码:
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,
emailLineColour = "#779ECB", phoneLineColour = "#FF6961", tooltipTextColour = "white";
var color = d3.scale.category10();
var nest = d3.nest()
.key(function (d) { return d.Type; })
.entries(data);
var x;
if (type == "month") {
var x = d3.scale.linear()
.domain([1, 31])
.range([0, width]);
} else if (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) { return x(d.xValue); })
.y(function (d) { return y(d.Value); });
var svg = placeholder.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 + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
if (type == "year") {
svg.append("g")
.attr("class", "x axis")
.append("text")
.attr("class", "axis-label")
.attr("transform", "none")
.attr("y", (-margin.left) + 530)
.attr("x", -height + 860)
.text('Month');
}
else if (type == "month") {
svg.append("g")
.attr("class", "x axis")
.append("text")
.attr("class", "axis-label")
.attr("transform", "none")
.attr("y", (-margin.left) + 525)
.attr("x", -height + 860)
.text('Day');
}
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "y axis")
.append("text")
.attr("class", "axis-label")
.attr("transform", "rotate(-90)")
.attr("y", (-margin.left) + 15)
.attr("x", -height / 2)
.text('Communications');
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 };
})
};
});
x.domain(d3.extent(nest, function (d) { return d.xVal; }));
y.domain([
d3.min(methods, function (m) { return d3.min(m.values, function (v) { return v.Value; }); }),
d3.max(methods, function (m) { return d3.max(m.values, function (v) { return v.Value; }); })
]);
var method = svg.selectAll('.method')
.data(methods)
.enter().append('g')
.attr('class', 'method');
method.append('path')
.attr('class', 'line')
.attr('d', function (d) { return line(d.values); })
.attr('stroke', function (d) { return color(d.commType); });
method.append('text')
.datum(function (d) { return { commType: d.commType, value: d.values[d.values.length - 1] }; })
.attr("transform", function (d) { return "translate(" + x(d.value.xVal) + "," + y(d.value.Value) + ")"; })
.attr('x', 3)
.attr('dy', '.35em')
.text(function (d) { return d.commType; });
if (callback) {
callback();
}
}
答案 0 :(得分:2)
对于StackOverflow,您的问题可能有点过于宽泛,但我会尝试提供帮助。我总是接近我的API输出数据的问题的方式是问我的数据将如何在前端消耗?在这种情况下,您尝试创建一个d3
多线图表,d3
将需要一个包含数据点数组的对象数组(此处为example很棒) 。在JSON中有类似的东西:
[
{
key: 'Email', //<-- identifies the line
values: [ //<-- points for the line
{
xVal: '20160101',
Value: 10
}, {
xVal: '20160102',
Value: 20
}, ...
]
}, {
key: 'Phone',
values: [
{
xVal: 'Jan',
Value: 30
}, {
xVal: '20160102',
Value: 25
}, ...
]
},
...
]
现在问题变成了如何将数据转换为这样的结构。给了好几个小时,你可能会写一个linq语句,它会做但是,我有点像返回一个扁平的JSON对象(毕竟如果我们正在编写一个可重用的restful接口,flat是最有用的)。那么,我们如何为易于使用的d3结构进行最后的跳转。鉴于你:
.Select(g => new
{
Type = g.Key.Method,
xVal = g.Key.Month,
Value = g.Count()
});
会生成一个JSON对象,如:
[{"Type":"Phone","xVal":"Feb","Value":1},{"Type":"Email","xVal":"Jan","Value":3},{"Type":"Phone","xVal":"Jan","Value":1}]
d3
然后可以到我们的&#34;易于使用&#34;格式一样简单:
var nest = d3.nest()
.key(function(d) { return d.Type; })
.entries(data);
产生:
[
{
"key":"Phone",
"values":[
{
"Type":"Phone",
"xVal":"Feb",
"Value":1
},
{
"Type":"Phone",
"xVal":"Jan",
"Value":1
}
]
},
{
"key":"Email",
"values":[
{
"Type":"Email",
"xVal":"Jan",
"Value":3
}
]
}
]
从这种结构中,您的多线图变得轻而易举......
评论的编辑
我真的不明白您尝试对某些代码执行的操作(特别是对于您的methods
变量 - 数据已经是d3
的一种很好的格式)。所以我重构了一下:
<!DOCTYPE html>
<html>
<head>
<script data-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]);
} else if (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) {
return x(d.xValue);
})
.y(function(d) {
return y(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');
} else if (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) {
return line(d.values);
})
.style('stroke', function(d) {
return color(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>
&#13;
编辑评论2
这实际上是一个棘手的问题。怎么样:
// for each dataset
nest.forEach(function(d){
// loop our domain
for (var i = x.domain()[0]; i <= x.domain()[1]; i++){
// if there's no xValue at that location
if (!d.values.some(function(v){ return (v.xValue === i) })){
// add a zero in place
d.values.splice((i - 1), 0, {xValue: i, Value: 0});
}
}
});
上面的代码示例也被编辑。