2015-12-16 136 views
10

我正在通過嵌套.csv文件構建分組條形圖。圖表也可以作爲折線圖查看,所以我想要一個適合線對象的嵌套結構。我原來的.csv看起來是這樣的:d3訪問分組條形圖中的嵌套數據

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); }); 

的摹元的罰款和單個條形圖正在映射到它們,並正確應用了x值和類。

我的問題是訪問數據的'rate'的高度和y值的酒吧。在上面的表格中給出了一個NaN。我已經使用類別數據附加g ^元素,然後追加rects用也試過:

.data(function(d) { return d.values }) 

這讓我訪問率數據,但所有36條映射到每個rangeBands的。

它也可以在平坦的數據結構中正常工作,但我似乎無法在嵌套兩個級別時使用它,儘管查看了大量示例和SO問題。

如何訪問費率數據?

針對西里爾的要求,這裏的全碼:

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 "<p id='date'>" + displayDate(d.date) + "</p><p id='value'>$" + 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); 

    }); 

更新2月18日'16。

看來我還沒有解釋我試圖做得足夠好。圖表的線條和條形版本將分開顯示,即用戶可以根據輸入的select元素查看其中的一個。另外請注意,我最初無法控制數據的輸入方式。

a version of exactly how it should work here.

當我還是通過它的工作這有人提出疑問,但我從來沒有解決這個問題 - 我用做數據的兩個獨立的巢一種解決方法。

+0

你可以發佈你的完整代碼..我認爲是有一個問題與yscale。 – Cyril

+0

嗨西里爾, 我會後在某一時刻的全部代碼,但我相信它不是問題yScale。我已經通過將一個數字放在d.rate的地方進行了檢查,並且在嵌套數據處於更平坦的格式時,它也可以工作。規模在下面。 var yScale = d3.scale.linear()。range([h,0]).nice(); – tgerard

+0

@tgerard確認 - 是否需要一個分組條形圖,其中日期爲x軸,值爲y軸?如果是這樣,目前還不清楚你打算如何使用類別對象。所提供的代碼查看d.rate的類別對象。但類別對象的形狀爲:{name:「Actual」,值:[{date:「」,rate:0}]}。所以它不會有屬性d.rate。你能確認你想要建立的是什麼,我可以建議如何最好地解決它? –

回答

1

的問題,我相信,是你有約束力的類別陣列到酒吧的選擇,這樣的:據我所看到

bars.selectAll("rect").data(categories) 

(內部消除運行演示)類別是一個只有四個值的數組(每個類別一個)。

您必須在嵌套數據結構中「深入」一步。

要畫一組條形爲每個類別你需要遍歷類別和陣列包含對選擇的實際值綁定值。

喜歡的東西:

categories.each(function (category) { 
    var klass = category.name; 
    bars.selectAll("rect ." + klass) 
     .data(category.values) 
     .enter() 
     .append("rect") 
     .attr("class", klass) 
     .attr("width", barWidth) 
     .attr("x", function (d, i) { /* omitted */}) 
     .attr("y", function (d) { return yScale(d.rate); }) 
     .attr("height", function (d) { return h - yScale(d.rate); }); 
    }); 

----編輯

,而不是上面的代碼,想想畫就像你用線做的吧。就像這樣:

var bars = svg.selectAll(".barGroup") 
    .data(categories) 
    .enter() 
    .append("g") 
    .attr("class", function (d) { return lineClass(d.name) + "Bar barGroup"; }) 
    .attr("transform", function (d, i) { 
     var x = i > 1 ? xScale.rangeBand()/2 : 0; 
     return "translate(" + x + ",0)"; 
    }) 
    .selectAll('rect') 
    .data(function (d) { return d.values; }) 
    .enter() 
    .append("rect") 
    .attr("class", "bar") 
    .attr("width", barWidth) 
    .attr("x", function (d, i) { return xScale(d.date); }) 
    .attr("y", function (d, i) { return yScale(d.rate); }) 
    .attr("height", function (d) { return h - yScale(d.rate); }); 
+0

是的,Doktorn,這正是我想要做的 - 在結構中進一步「深入」一步。該解決方案可能是正確的。 不幸的是,這個項目有點老了,我找到了一個解決方法,所以我必須做一些重建,以便在12月份進行測試。我現在不能這樣做,但明天將會陷入困境(澳大利亞時間)。 – tgerard

+0

@Dotkom我改變了你的'categories.each'到'categories.forEach'來解決一個錯誤,否則按原樣運行。它將所有36個矩形添加到每個rangeBand中,即在7月份的位置上有36個數據點,而在8月份則有36個,以此類推。有[這裏的一個例子:](http://lowercasen.com/dev/tests/stackoverflowTest.html) – tgerard

4

鏈接的jsfiddle: https://jsfiddle.net/sladav/rLh4qwyf/1/

我認爲這個問題的根源在於,要使用兩個變量不明確自己的原始數據集有:(1)類別(2)評分

您的數據格式設置爲寬格式,因爲每個類別都有自己的變量,速率值存在於月份和某個給定類別的十字路口。我認爲你最終的嵌套方式是或至少應該解決這個問題,但是我不清楚翻譯中是否存在某些東西。從概念上講,我認爲從一個與你正在努力完成的組織相匹配的組織開始更有意義。我重新格式化原始數據,並再次走近它 - 在概念水平嵌套似乎簡單明瞭...

新列:

  • 月:時間變量;映射到X軸
  • 類別:分類值[Actual,Forecast,Budget];用於分組/顏色
  • 評分:數值;映射到Y軸

改組CSV(空值下降):

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,以便將數據明確地與類別變量開始。現在你的數據存在兩層。第一層有三個組(每個類別一個)。第二層包含每行/一組酒吧的RATE數據。您還必須嵌套數據選擇 - 第一層用於繪製線條,第二層用於繪製線條。

嵌套數據:

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

您的分組,一線數據創建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) 

要顯示無論是酒吧或(不能同時)

創建一個選擇酒吧/線和轉換功能/切換它們的可見性/不透明度。當您的下拉輸入發生變化並且將下拉輸入作爲函數的輸入時運行。

+0

嗨,謝謝你幫助我。爲什麼我採取了我的方法有幾個原因。一個是條形圖版本是一個分組條形圖,所以我必須將嵌套分類。另一個原因是我無法控制數據的原始形式 - 它來自客戶端。 我已經發布了一個問題的更新,其中包含一個鏈接,看看它的外觀和功能。然而,這個問題仍然是相關的,因爲我使用了兩個獨立的數據巢來實現結果,這對我來說似乎是無效的。 – tgerard

+0

@tgerard你的工作版本看起來不錯!我在回答中添加了一個編輯,只是爲了解釋如何擴展我所需要的那些額外的「功能」。至於你的方法的「低效率」,我認爲這可能是你的原始數據的組織所必需的。你可以先用javascript重新格式化,然後跟隨我的所有內容,但在這一點上,堅持使用你所擁有的東西(除非你發現自己必須反覆對抗那種低效率的方法)可能更有意義。 –

+0

是的,我會堅持我原來的做法。當我第一次發佈這個問題時,我認爲回答這個問題對於那些編程技能比我的編程技巧更好的人來說很容易,但是我現在已經得出結論,這實際上很難,我不應該害怕有兩個巢。再次感謝給它一個裂縫。 – tgerard