2015-12-16 18 views
10

Buduję zgrupowany wykres słupkowy zagnieżdżając plik .csv. Wykres będzie również widoczny jako wykres liniowy, więc chcę struktury zagnieżdżania, która pasuje do obiektu linii. Mój oryginalny .csv wygląda następująco:d3 uzyskiwanie dostępu do zagnieżdżonych danych w zgrupowanym wykresie słupkowym

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 

i mój zagnieżdżenia jest tak:

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

      }); 

Kod budować mój wykres jest:

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

Elementy G są w porządku i poszczególne paski są na nich odwzorowywane, przy czym wartość x i klasa są poprawnie stosowane.

Mój problem polega na uzyskaniu dostępu do danych dla "stawki" dla wysokości i wartości y słupków. W powyższym formularzu daje NaN. Ja również próbowałem przy użyciu danych kategorii, do dołączania elementów G i następnie dołączając prostokąty, z:

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

To pozwala mi na dostęp do danych stóp, ale odwzorowuje wszystkie 36 barów do każdego z rangeBands.

To również działa dobrze w bardziej płaskiej strukturze danych, ale nie mogę go użyć, gdy jest zagnieżdżony na dwa poziomy w dół, mimo że przegląda wiele przykładów i pytań SO.

Jak uzyskać dostęp do danych o kursie?

W odpowiedzi na wniosek Cyryla, oto pełny kod:

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

    }); 

Aktualizacja 18 lutego '16.

Wygląda na to, że nie wyjaśniłem, co starałem się zrobić wystarczająco dobrze. Wersje liniowe i słupkowe wykresu będą widoczne osobno, tj. Użytkownicy zobaczą jeden zgodnie z wprowadzeniem do elementu wyboru. Zauważ też, że nie mam kontroli nad tym, w jaki sposób dane przychodzą na początku.

mam a version of exactly how it should work here.

Ta kwestia została podniesiona kiedy nadal działa przez niego, ale nigdy nie rozwiązało problemu - Kiedyś obejście prowadzenia dwóch oddzielnych gniazd danych.

+0

czy możesz opublikować swój pełny kod ... i myślisz, że jest problem z yscale. – Cyril

+0

Cześć Cyril, Założę pełny kod za chwilę, ale jestem pewien, że nie jest to kwestia YScale. Sprawdziłem, wstawiając numer w miejsce d.rate, a także sprawiłem, że działa, gdy zagnieżdżone dane są bardziej płaskie. Skala jest poniżej. var yScale = d3.scale.linear(). Range ([h, 0]) .nice(); – tgerard

+0

@tgerard Aby potwierdzić - czy chcesz pogrupować wykres słupkowy z datami jako osią x i wartościami jako oś y? Jeśli tak, to nie jest jasne, w jaki sposób planujesz użyć obiektu kategorii. Podany kod znajduje się w obiekcie kategorii dla d.rate. Ale obiekt kategorii ma kształt: {nazwa: "Rzeczywisty", wartości: [{data: "", stawka: 0}]}. Więc nie będzie miał na nim właściwości d.rate. Czy możesz potwierdzić, co próbujesz zbudować, i mogę doradzić, jak najlepiej to rozwiązać? –

Odpowiedz

1

Problem, wierzę, że jesteś wiązania kategorie tablicę do wyboru barów, jak to:

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

O ile widzę (whithout uruchomionego demo) kategoriach jest tablicą zawierającą tylko cztery wartości (po jednej dla każdej kategorii).

Musisz iść o krok "głębiej" w swojej zagnieżdżonej strukturze danych.

Aby narysować zestaw barów dla każdej kategorii będzie trzeba iteracyjne nad kategorie i wiążą Wartości tablicę, która zawiera rzeczywiste wartości do wyboru.

Coś jak:

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

---- Edycja

Zamiast powyższego kodu, pomyśl o rysowanie prętów podobnie jak zrobić z liniami. Tak:

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

Tak, Doktorn, dokładnie to chcę zrobić - wejdź o jeden stopień "głębiej" w strukturę. To rozwiązanie jest prawdopodobnie poprawne. Niestety, ten projekt jest nieco stary i znalazłem dla niego obejście, więc muszę przeprowadzić rekonstrukcję miejsca, w którym byłem w grudniu, aby przetestować to. Nie mogę tego teraz zrobić, ale utknę w tym jutro (czas australijski). – tgerard

+0

@Dotkom Zmieniłem twoje "categories.each" na "categories.forEach", aby obejść błąd, ale w przeciwnym razie uruchomiłem go jako taki. Dodaje wszystkie 36 prostokątów do każdego zakresu pasma, tj. Jest pasek dla każdego z 36 punktów danych w pozycji lipcowej, kolejnych 36 w sierpniu i tak dalej. Jest [przykład tego tutaj:] (http://lowercasen.com/dev/tests/stackoverflowTest.html) – tgerard

4

Link do jsfiddle: https://jsfiddle.net/sladav/rLh4qwyf/1/

myślę źródłem problemu jest to, że chcesz użyć dwóch zmiennych, które wyraźnie nie istnieją w oryginalnym zestawie danych: (1) Kategoria i (2) Oceń.

Twoje dane są sformatowane w szerokim formacie, ponieważ każda kategoria otrzymuje własną zmienną, a wartość stawki istnieje na rozdrożu miesiąca i jednej z podanych kategorii. Myślę, że ostateczny sposób zagnieżdżania się jest lub powinien rozwiązać ten problem, ale nie jest dla mnie jasne, czy coś jest nie tak w tłumaczeniu. Pojęciowo myślę, że lepiej zacząć od organizacji, która pasuje do tego, co próbujesz osiągnąć. I sformatowany oryginalne dane i zbliżył go ponownie - na płaszczyźnie koncepcyjnej gniazdowania wydaje się oczywiste i proste ...

nowe kolumny:

  • Miesiąc: Zmienny czas; zmapowany do osi X
  • Kategoria: Wartości kategoryczne [Rzeczywiste, Prognoza, Budżet]; używany do grupowania/kolorowania
  • Stawka: wartość numeryczna; odwzorowywane na osi Y

Zreorganizowane CSV (spadła 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 

Ze swoimi nowo sformatowanych danych, należy uruchomić za pomocą d3.nest grupować dane wyraźnie ze zmienną kategorii. Teraz Twoje dane istnieją w dwóch warstwach. Pierwsza warstwa ma trzy grupy (po jednej dla każdej kategorii). Druga warstwa zawiera dane RATE dla każdej linii/zestawu słupków. Trzeba również zagnieździć wybrane dane - pierwsza warstwa służy do rysowania linii, druga warstwa dla prętów.

Zagnieżdżanie swoje dane:

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

Tworzenie grup svg dla zgrupowanych, danych od 1 tier:

d3.select(".plot-space").selectAll(".g-category") 
    .data(nestedData) 
    .enter().append("g") 
    .attr("class", "g-category") 

korzystać z tych danych, aby dodać swoje linie/ścieżki:

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

Na koniec "przejdź do" drugiej warstwy, aby dodać paski/rect:

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

Jest to podejście proste (przynajmniej dla mnie), ponieważ bierze się je po jednej kategorii za jednym razem, używając zgrupowanych danych, aby narysować linię, a następnie poszczególne punkty danych, aby narysować paski.

LAZY EDIT:

Aby uzyskać strony Kategoria Bary siebie

Tworzenie odwzorowania skali porządkowej kategorię do [1, Ncategories]. Służy do dynamicznego przesunięcia bary z czymś jak

translate(newScale(category)*barWidth) 

Aby pokazać albo pasków lub linii (nie oba)

utworzyć funkcję, która wybiera bary/linie i przejścia/przełącza ich widoczność/krycie. Uruchamiaj się, gdy zmienia się twoje dane wejściowe z rozwijanym wejściem jako dane wejściowe do funkcji.

+0

Cześć i dzięki za pomoc. Jest kilka powodów, dla których podjąłem podejście, które mam. Po pierwsze, wersja z wykresem słupkowym to zgrupowany wykres słupkowy, więc muszę zagnieżdżać się w kategorie. Innym jest to, że nie mam kontroli nad pierwotną formą danych - pochodzi od klienta. Opublikowałem aktualizację pytania, które zawiera link do dokładnie tego, jak powinien wyglądać i funkcjonować.Jednak to pytanie jest nadal aktualne, ponieważ wykorzystałem dwa oddzielne gniazda danych, aby osiągnąć wynik, co wydaje mi się mało skuteczne. – tgerard

+0

@tgerard twoja działająca wersja wygląda świetnie! Dodałem komentarz do mojej odpowiedzi, aby wyjaśnić, jak można rozszerzyć to, co mam, aby uzyskać te dodatkowe "funkcje", których szukałeś. Jeśli chodzi o "nieskuteczność" twojego podejścia, myślę, że może to być konieczne w org oryginalnych danych. Możesz zacząć od ponownego sformatowania z javascript, a następnie podążać za tym, co mam, ale w tym momencie prawdopodobnie lepiej będzie trzymać się tego, co masz (chyba, że ​​musisz wielokrotnie walczyć z tym nieefektywnym podejściem). –

+0

Tak, po prostu trzymam się mojego oryginalnego podejścia. Kiedy po raz pierwszy opublikowałem to pytanie, myślałem, że odpowiedź na to pytanie będzie łatwa dla kogoś, kto ma lepsze umiejętności kodowania niż moje, ale doszedłem do wniosku, że jest to dość trudne i nie powinienem się bać dwóch gniazd. Jeszcze raz dziękuję za to. – tgerard

Powiązane problemy