我正在通过嵌套.csv文件来构建分组条形图.该图表也可以作为折线图查看,因此我想要一个适合线对象的嵌套结构.我原来的.csv看起来像这样:
Month,Actual,Forecast,Budget Jul-14,200000,-,74073.86651 Aug-14,198426.57,-,155530.2499 Sep-14,290681.62,-,220881.4631 Oct-14,362974.9,-,314506.6437 Nov-14,397662.09,-,382407.67 Dec-14,512434.27,-,442192.1932 Jan-15,511470.25,511470.25,495847.6137 Feb-15,-,536472.5467,520849.9105 Mar-15,-,612579.9047,596957.2684 Apr-15,-,680936.5086,465313.8723 May-15,-,755526.7173,739904.081 Jun-15,-,811512.772,895890.1357
我的嵌套是这样的:
d3.csv("data/net.csv", function(error, data) { if (error) throw error; var headers = d3.keys(data[0]).filter(function(head) { return head != "Month"; }); data.forEach(function(d) { d.month = parseDate(d.Month); }); var categories = headers.map(function(name) { return { name: name, // "name": the csv headers except month values: data.map(function(d) { return { date: d.month, rate: +(d[name]), }; }), }; });
构建我的图表的代码是:
var bars = svg.selectAll(".barGroup") .data(data) // Select nested data and append to new svg group elements .enter() .append("g") .attr("class", "barGroup") .attr("transform", function (d) { return "translate(" + xScale(d.month) + ",0)"; }); bars.selectAll("rect") .data(categories) .enter() .append("rect") .attr("width", barWidth) .attr("x", function (d, i) { if (i < 2) {return 0;} else {return xScale.rangeBand() / 2;}}) .attr("y", function (d) { return yScale(d.rate); }) .attr("height", function (d) { return h - yScale(d.rate); }) .attr("class", function (d) { return lineClass(d.name); });
g元素很好,各个条被映射到它们,正确应用了x值和类.
我的问题在于访问条形的高度和y值的'rate'数据.在上面的表格中,它给出了NaN.我还尝试使用类别数据附加g元素,然后附加rects:
.data(function(d) { return d.values })
这允许我访问速率数据,但将所有36个条形图映射到每个rangeBands.
它在一个更扁平的数据结构中也可以正常工作,但是当它嵌套两个级别时我似乎无法使用它,尽管通过大量的例子和SO问题.
如何访问费率数据?
根据Cyril的要求,这里是完整的代码:
var margin = {top: 20, right: 18, bottom: 80, left: 50}, w = parseInt(d3.select("#bill").style("width")) - margin.left - margin.right, h = parseInt(d3.select("#bill").style("height")) - margin.top - margin.bottom; var customTimeFormat = d3.time.format.multi([ [".%L", function(d) { return d.getMilliseconds(); }], [":%S", function(d) { return d.getSeconds(); }], ["%I:%M", function(d) { return d.getMinutes(); }], ["%I %p", function(d) { return d.getHours(); }], ["%a %d", function(d) { return d.getDay() && d.getDate() != 1; }], ["%b %d", function(d) { return d.getDate() != 1; }], ["%b", function(d) { return d.getMonth(); }], ["%Y", function() { return true; }] ]); var parseDate = d3.time.format("%b-%y").parse; var displayDate = d3.time.format("%b %Y"); var xScale = d3.scale.ordinal() .rangeRoundBands([0, w], .1); var xScale1 = d3.scale.linear() .domain([0, 2]); var yScale = d3.scale.linear() .range([h, 0]) .nice(); var xAxis = d3.svg.axis() .scale(xScale) .tickFormat(customTimeFormat) .orient("bottom"); var yAxis = d3.svg.axis() .scale(yScale) .orient("left") .innerTickSize(-w) .outerTickSize(0); var svg = d3.select("#svgCont") .attr("width", w + margin.left + margin.right) .attr("height", h + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var thous = d3.format(",.0f") var lineClass = d3.scale.ordinal().range(["actual", "forecast", "budget"]); var tip = d3.tip() .attr('class', 'd3-tip') .offset([-10, 0]) .html(function(d) { return "" + displayDate(d.date) + "
$" + thous(d.rate); }) d3.csv("data/net.csv", function(error, data) { if (error) throw error; var headers = d3.keys(data[0]).filter(function(head) { return head != "Month"; }); data.forEach(function(d) { d.month = parseDate(d.Month); }); var categories = headers.map(function(name) { return { name: name, values: data.map(function(d) { return { date: d.month, rate: +(d[name]), }; }), }; }); var min = d3.min(categories, function(d) { return d3.min(d.values, function(d) { return d.rate; }); }); var max = d3.max(categories, function(d) { return d3.max(d.values, function(d) { return d.rate; }); }); var minY = min < 0 ? min * 1.2 : min * 0.8; xScale.domain(data.map(function(d) { return d.month; })); yScale.domain([minY, (max * 1.1)]); var barWidth = headers.length > 2 ? xScale.rangeBand() / 2 : xScale.rangeBand() ; svg.call(tip); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + h + ")") .call(xAxis); svg.append("g") .attr("class", "y axis") .call(yAxis); var bars = svg.selectAll(".barGroup") .data(data) .enter() .append("g") .attr("class", "barGroup") .attr("transform", function (d) { return "translate(" + xScale(d.month) + ",0)"; }); bars.selectAll("rect") .data(categories) .enter() .append("rect") .attr("width", barWidth) .attr("x", function (d, i) { if (i < 2) {return 0;} else {return xScale.rangeBand() / 2;}}) .attr("y", function (d) { return yScale(d.rate); }) .attr("height", function (d) { return h - yScale(d.rate); }) .attr("class", function (d) { return lineClass(d.name) + " bar"; }); var legend = svg.selectAll(".legend") .data(headers) .enter() .append("g") .attr("class", "legend"); legend.append("line") .attr("class", function(d) { return lineClass(d); }) .attr("x1", 0) .attr("x2", 40) .attr("y1", function(d, i) { return (h + 30) + (i *14); }) .attr("y2", function(d, i) { return (h + 30) + (i *14); }); legend.append("text") .attr("x", 50) .attr("y", function(d, i) { return (h + 32) + (i *14); }) .text(function(d) { return d; }); svg.selectAll(".bar") .on('mouseover', tip.show) .on('mouseout', tip.hide); });
2016年2月18日更新.
似乎我没有解释我试图做得足够好.图表的线条和条形版本将单独看到,即用户可以根据select元素的输入看到任一个.另请注意,我无法控制数据最初的来源.
我有一个完全如何在这里工作的版本.
当我还在努力解决这个问题时,我提出了这个问题,但是我从来没有解决过这个问题 - 我使用了一个解决方法来做两个独立的数据嵌套.
链接到jsfiddle:https://jsfiddle.net/sladav/rLh4qwyf/1/
我认为问题的根源在于您要使用原始数据集中未明确存在的两个变量:(1)类别和(2)速率.
您的数据格式为宽格式,每个类别都有自己的变量,而rate的值存在于月份和给定类别之一的十字路口.我认为你最终的嵌套方式至少应该解决这个问题,但我不清楚翻译中是否存在某些东西或哪些内容会丢失.从概念上讲,我认为从一个与你想要完成的事情相匹配的组织开始更有意义.我重新格式化了原始数据并再次接近它 - 从概念上讲,嵌套似乎简单明了......
新栏目:
月:时间变量; 映射到X轴
类别:分类值[实际,预测,预算]; 用于分组/颜色
比率:数值; 映射到Y轴
重组CSV(删除NULL):
Month,Category,Rate Jul-14,Actual,200000 Aug-14,Actual,198426.57 Sep-14,Actual,290681.62 Oct-14,Actual,362974.9 Nov-14,Actual,397662.09 Dec-14,Actual,512434.27 Jan-15,Actual,511470.25 Jan-15,Forecast,511470.25 Feb-15,Forecast,536472.5467 Mar-15,Forecast,612579.9047 Apr-15,Forecast,680936.5086 May-15,Forecast,755526.7173 Jun-15,Forecast,811512.772 Jul-14,Budget,74073.86651 Aug-14,Budget,155530.2499 Sep-14,Budget,220881.4631 Oct-14,Budget,314506.6437 Nov-14,Budget,382407.67 Dec-14,Budget,442192.1932 Jan-15,Budget,495847.6137 Feb-15,Budget,520849.9105 Mar-15,Budget,596957.2684 Apr-15,Budget,465313.8723 May-15,Budget,739904.081 Jun-15,Budget,895890.1357
使用新格式化的数据,首先使用d3.nest使用CATEGORY变量显式地对数据进行GROUP.现在您的数据分为两层.第一层有三个组(每个类别一个).第二层包含每行/每组条的RATE数据.您还必须嵌套数据选择 - 第一层用于绘制线条,第二层用于条形图.
嵌套数据:
var nestedData = d3.nest() .key(function(d) { return d.Category;}) .entries(data)
为分组的第1层数据创建svg组:
d3.select(".plot-space").selectAll(".g-category") .data(nestedData) .enter().append("g") .attr("class", "g-category")
使用此数据添加您的行/路径:
d3.selectAll(".g-category").append("path") .attr("class", "line") .attr("d", function(d){ return lineFunction(d.values);}) .style("stroke", function(d) {return color(d.key);})
最后,"步入"第二层添加条形/矩形:
d3.selectAll(".g-category").selectAll(".bars") .data(function(d) {return d.values;}) .enter().append("rect") .attr("class", "bar") .attr("x", function(d) {return x(d.Month);}) .attr("y", function(d) {return y(d.Rate);}) .attr("width", 20) .attr("height", function(d) {return height - y(d.Rate)}) .attr("fill", function(d) {return color(d.Category)})
这是一种简单的方法(至少对我来说),因为你一次只使用一个类别,使用分组数据绘制一条线,然后使用各个数据点绘制条形图.
懒惰编辑:
要并排获得类别栏
创建序数比例映射类别为[1,nCategories].使用它来动态偏移条形图
translate( newScale(category)*barWidth )
显示条形或线条(不是两者)
创建一个选择条形图/线条和过渡/切换其可见性/不透明度的函数.当您的下拉输入更改并使用下拉输入作为函数的输入时运行.