2015-12-16 24 views
10

Bir .csv dosyası yerleştirerek gruplandırılmış bir çubuk grafik yapıyorum. Grafik ayrıca bir çizgi grafik olarak görülebilir, bu yüzden çizgi nesnesine uyan bir yuvalama yapısı istiyorum.d3 Gruplandırılmış çubuk grafikte iç içe geçmiş verilere erişme

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 

ve benim yuvalama şu şekildedir:: My orijinal .csv şöyle Çizelgemi inşa etmek

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

      }); 

kodudur:

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

gr elemanları ince ve Tek tek çubuklar, x değeri ve sınıf doğru şekilde uygulandığında, onlara eşlenir.

Sorunum, çubukların yükseklik ve y değeri için 'oran' verilerine erişmeye geliyor. Yukarıdaki formda bir NaN verir. Ben de birlikte rects gr öğelerini eklemeniz kategori verilerini kullanarak ve daha sonra ekleme denedim:

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

Bu bana oran verilere erişmek için izin verir, fakat rangeBands her birine 36 bar eşler.

Ayrıca, daha düz bir veri yapısında gayet iyi çalışıyor, ancak çok sayıda örnek ve SO sorularına bakmasına rağmen, iki seviye aşağı yuvalandığında onu kullanamıyorum.

Ücret verisine nasıl erişirim?

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

    }); 

Güncelleme 18 Şubat '16: Cyril'ın talebine yanıt olarak

, burada tam kod.

Yeterince iyi yapmaya çalıştığım şeyi açıklamamışım gibi görünüyor. Çizelgenin çizgi ve çubuk versiyonları ayrı olarak görülecektir, yani kullanıcılar bir seçime göre girdiye göre birini görebilirler. Ayrıca, başlangıçta verilerin nasıl geldiğine dair kontrolüm olmadığını da unutmayın.

Ben a version of exactly how it should work here.

Bu soru hala içinden çalışırken kaldırdı ama sorunu çözüldü asla - Ben verinin iki ayrı yuvalar yapmanın bir geçici çözüm kullanılır.

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

Bildiğim kadarıyla gördüğünüz gibi kategorilerde (çalışan bir demo whithout):

+0

Tam kodunuzu gönderirsiniz ... yscale ile ilgili bir sorun olduğunu düşünün. – Cyril

+0

Merhaba Cyril, birazdan tam kod göndeririz, ama söz konusu yScale değil eminim. Ben d.rate yerine bir sayı koyarak kontrol ettik ve ben de iç içe geçmiş veri daha düz bir biçimde olduğunda çalıştım. Ölçek aşağıdadır. var yScale = d3.scale.linear(). Aralığı ([h, 0]) .nice(); – tgerard

+0

@tgerard onaylamak için - Eğer y ekseni olarak x ekseni olarak tarih ve değerlere sahip bir gruplandırılmış çubuk grafik istiyorsun? Öyleyse, kategoriler nesnesini nasıl kullanmayı planladığınız belli değildir. Sağlanan kod, d.rate için kategoriler nesnesine bakmaktır. Ancak kategoriler nesnesi şu şekildedir: {name: "Gerçek", değerler: [{date: "", rate: 0}]}. Yani mülkün üzerinde durmayacak. Oluşturmaya çalıştığınız şeyi doğrulayabilir misiniz ve bunu en iyi nasıl çözeceğimize dair tavsiyede bulunabilir miyim? –

cevap

1

sorun, ben inanıyoruz, böyle, kategori barlar seçime dizi bağlayıcı olmasıdır sadece dört değer içeren bir dizidir (her kategori için bir tane).

İç içe geçmiş veri yapınızda bir adım daha 'derin' gitmelisiniz.

Eğer üzerinde kategorileri yineleme ve Degerler seçime gerçek değerleri içeren dizisi bağlamak gerekir her bir kategori için barlar bir dizi çizmek için.

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

---- Yerine Yukarıdaki kod

Düzenleme

, hatları ile ne gibi çubukları çizim düşünün: gibi

şey. Şunun gibi:

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

Evet, Doktorn, tam olarak yapmak istediğim şey - yapıya bir adım daha 'derin' erişim. Bu çözüm muhtemelen doğrudur. Ne yazık ki, bu proje biraz eski ve bunun için bir geçici çözüm buldum, bu yüzden bunu test etmek için Aralık ayında bulunduğum yere yeniden inşası yapmak zorundayım. Bunu şimdi yapamam, ama yarın (Avustralya saati) içine sıkışmış olacak. – tgerard

+0

@Dotkom ben bir hata etrafında almak için categories.forEach' 'için' categories.each' değiştirdi ama olduğu gibi aksi takdirde bu koştu. Tüm 36 adet dikdörtgeni rangeBands'ın her birine ekliyor, yani July'nin 36 veri noktası için birer çubuk var, başka bir 36'sı da Ağustos'ta. [Bunun bir örneği burada:] var (http://lowercasen.com/dev/tests/stackoverflowTest.html) – tgerard

4

Bağlantı jsfiddle için: https://jsfiddle.net/sladav/rLh4qwyf/1/

Ben sorunun kök açıkça orijinal veri setinde bulunmayan iki değişken kullanmak istediğinizi olduğunu düşünüyorum: (1) Kategori ve (2) Hızı.

Verileriniz, her bir kategorinin kendi değişkenine sahip olduğu ve oranın ayın kesişme noktalarında ve verilen kategorilerden birinde mevcut olduğu biçimiyle geniş bir biçimde biçimlendirilmiştir. Nihayetinde iç içe geçmiş olmanın, ya da en azından bunu ele alması gerektiğini düşünüyorum, fakat çeviride bir şeyin kaybolması ya da kaybolması bana açık değil. Kavramsal olarak, başarmaya çalıştığınız şeyle eşleşen bir organizasyonla başlamanın daha mantıklı olduğunu düşünüyorum. Ben orijinal verileri yeniden biçimlendirilmiş ve tekrar yaklaştı - ...

yeni sütunlar yuvalama basit ve sade görünüyor kavramsal düzeyde:

  • Ay: Zaman Değişken; X eksenine eşlenmiş
  • Kategori: Kategorik değerler [Gerçek, Tahmin, Bütçe]; grup/renk için kullanılır
  • Hızı: Sayısal değer; Y ekseni CSV yeniden yapılandırıldı

eşleştirilmiş (boş değerlere düştü): yeni biçimlendirilen verilerle

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 

, sen KATEGORİ değişkenle açıkça GRUBU verilerinizi d3.nest kullanarak başlayın. Şimdi verileriniz iki kat olarak var. İlk kademe üç gruba sahiptir (her kategori için bir tane). İkinci katman, her satır/çubuk grubu için RATE verilerini içerir. Veri seçimlerinizi de yerleştirmelisiniz - ilk katman çizgileri çizmek için kullanılır, ikinci çubuklar için katman. senin çizgi/yolları eklemek için

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

bunu kullanın veriler::

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

sizin gruplanmış, 1 katmanlı veri için svg grupları oluşturun:

verilerinizi yuvalama

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

Son olarak, "adım adım" 2. sütun eklemek için/rect:

Bu, basit bir yaklaşımdır (en azından benim için), bir satırda bir veri almak için gruplandırılmış veriyi kullanarak bir seferde bir kategori alırsınız, daha sonra çubukları çizmek için tek tek veri noktaları işaret eder.

TEMBEL DÜZENLEME:

[1, Ncategories] olarak sıralı ölçek haritalama Kategori oluşturma tarafında

tarafından kategori çubukları yan almak için. Bu dinamik barlar/hatları ve geçişler seçen bir işlev oluşturun ya çubukları veya çizgileri (ikisi birden değil)

göstermek için

translate(newScale(category)*barWidth) 

gibi bir şeyle çubuklarını dengelemek için kullanın/görünürlük/opaklık geçiş yapar. Açılır girişiniz değiştiğinde ve açılan giriş ile işlev girdisi olduğunda çalıştırın.

+0

Merhaba ve bana yardım ettiğin için teşekkürler. Sahip olduğum yaklaşımı almamın birkaç nedeni var. Biri, çubuk grafik sürümünün gruplandırılmış bir çubuk grafik olması, bu yüzden kategorileri iç içe geçirmem gerekiyor. Bir diğeri, verilerin orijinal formu üzerinde kontrol sahibi olmamam - müşteriden geliyor. Tam olarak nasıl görünmesi ve işlev görmesi için bir bağlantı içeren soruna bir güncelleştirme gönderdim.Ancak, sonuç elde etmek için iki ayrı veri yuvası kullandığım için bu soru hala geçerli, ki bu bana verimsiz geliyor. – tgerard

+0

@tgerard çalışma sürümünüz harika görünüyor! Cevabımla ilgili bir düzenleme ekledim, sadece aradığınız bu ekstra "özellikleri" ne almam gerektiğini genişletmenin nasıl mümkün olduğunu açıklamak için. Yaklaşımınızın "verimsizliği" ne kadar, orijinal verilerinizin org ile gerekli olabileceğini düşünüyorum. Javascript ile yeniden biçimlendirerek başlayabilir ve daha sonra sahip olduğum şeye uymayı başarabilirsin, ama bu noktada muhtemelen sahip olduğunuz şeyle uğraşmak daha mantıklı geliyor (kendinizi bu etkisiz yaklaşıma karşı tekrar tekrar savaşmak zorunda kalmazsanız). –

+0

Evet, sadece orijinal yaklaşımımla bağlı olacağım. Bu soruyu ilk gönderdiğimde, daha iyi kodlama becerilerine sahip birinin benimkinden daha kolay olduğunu düşünürdüm, ama şimdi gerçekten oldukça zor olduğu ve iki yuvaya sahip olmaktan korkmamam gerektiği sonucuna vardım. Bir çatlak verdiğiniz için tekrar teşekkürler. – tgerard

İlgili konular