2015-12-16 2 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 и класс применяются правильно.

Моя проблема связана с доступом к данным для «ставки» для значений высоты и у баров. В приведенной выше форме дается NaN. Я также попытался использовать данные категории для добавления г элементов, а затем добавляя прямоугольники с:

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

Это позволяет мне получить доступ к данным скорости, но отображает все 36 баров, чтобы каждый из rangeBands.

Он также отлично работает в более плотной структуре данных, но я не могу использовать его, когда он вложен на два уровня, несмотря на то, что он просматривает множество примеров и вопросов.

Как получить доступ к данным о скорости?

В ответ на просьбу Кирилла, вот полный код:

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

    }); 

Update 18 февраля '16.

Кажется, я не объяснил, что я пытался сделать достаточно хорошо. Линейные и штриховые варианты диаграммы будут видны отдельно, то есть пользователи могут видеть один из них в соответствии с вводом элемента выбора. Также обратите внимание, что я не контролирую, как данные поступают изначально.

У меня есть a version of exactly how it should work here.

Этот вопрос был поднят, когда я еще работал через него, но я никогда не был решен вопрос - я использовал обходной делать два отдельных гнезда данных.

+0

Можете ли вы опубликовать полный код. Я думаю, что есть проблема с yscale. – Cyril

+0

Hi Cyril, Я отправлю полный код за мгновение, но я уверен, что это не проблема yScale. Я проверил, поставив число вместо d.rate, и я также работал, когда вложенные данные находятся в более плотном формате. Шкала ниже. var yScale = d3.scale.linear(). Range ([h, 0]) .nice(); – tgerard

+0

@tgerard Чтобы подтвердить - вам нужна сгруппированная гистограмма с датами в виде оси x и значениями в качестве оси y? Если это так, неясно, как вы планируете использовать объект категорий. Приведенный код выглядит в объекте категорий для d.rate. Но объект категории имеет форму: {name: "Actual", values: [{date: "", rate: 0}]}. Так что у него не будет свойства d.rate на нем. Можете ли вы подтвердить, что именно вы пытаетесь построить, и я могу посоветовать, как лучше всего ее решить? –

ответ

1

Проблемы, я верю, что вы Привязывание Категории массива к выбору баров, например:

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

Насколько я могу видеть (Whithout бегущего дем) категории представляет собой массив с четырьмя значениями (по одному для каждой категории).

Вы должны сделать один шаг «глубже» в своей структуре вложенных данных.

Чтобы нарисовать множество баров для каждой категории, вам нужно будет перебрать категории и свяжите значения от массива, который содержит фактические значения для выбора.

Что-то вроде:

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

Да, Докторн, это именно то, что я хочу сделать - доступ на один шаг «глубже» в структуру. Это решение, вероятно, правильно. К сожалению, этот проект немного устарел, и я нашел для него обходной путь, поэтому мне нужно провести некоторую реконструкцию, где я был в декабре, чтобы проверить его. Я не могу сделать это сейчас, но застрял в нем завтра (по австралийскому времени). – tgerard

+0

@Dotkom Я изменил ваши 'categories.each' на' categories.forEach', чтобы обойти ошибку, но в противном случае это было как есть. Он добавляет все 36 прямоугольников в каждую из диапазонов диапазонов, т. Е. Есть бара для каждого из 36 точек данных в июле, еще 36 в августе и т. Д. Здесь [пример:] (http://lowercasen.com/dev/tests/stackoverflowTest.html) – tgerard

4

Ссылка на jsfiddle: https://jsfiddle.net/sladav/rLh4qwyf/1/

Я думаю, что корень этого вопроса является то, что вы хотите использовать две переменные, которые явно не существуют в исходном наборе данных: (1) Категория и (2) Оценка.

Ваши данные форматируются в широком формате, поскольку каждая категория получает свою собственную переменную, а значение ставки существует на перекрестке месяца и одной из данных категорий. Я думаю, что способ, которым вы гнездились, в конечном счете, или, по крайней мере, должен решить это, но мне непонятно, где или где что-то теряется в переводе. Концептуально, я думаю, что имеет смысл начать с организации, которая соответствует тому, что вы пытаетесь выполнить. Я переформатировать исходные данные и подошел к ней снова - на концептуальном уровне вложенности кажется простым и простым ...

НОВЫЕ КОЛОННЫ:

  • Месяц: Время переменной; сопоставлено с осью X
  • Категория: Категориальные значения [Фактические, прогноз, бюджет]; используемый для группировки/цвет
  • Оценка: Числовое значение; отображается на Y оси

Реорганизовано CSV (упал NULLs):

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

Наконец, «шаг в» 2-й уровень для добавления баров/прямоугольника:

Это простой подход (по крайней мере, для меня), поскольку вы берете его по одной категории за раз, используя сгруппированные данные для рисования линии, затем отдельные данные указывают на рисование баров.

LAZY EDIT:

Чтобы получить категорию баров рядом

Создать порядковое категорию масштаба отображения [1, nCategories]. Используйте это, чтобы динамически смещение полос с чем-то вроде

translate(newScale(category)*barWidth) 

Чтобы показать либо бруски или линии (не так)

Создайте функцию, которая выбирает бары/линии и переходы/переключает их видимость/непрозрачность. Запускайте, когда ваш раскрывающийся вход изменяется и с выпадающим входом в качестве входа в функцию.

+0

Привет и спасибо, что помогли мне. Есть несколько причин, почему я взял подход, который у меня есть. Первая заключается в том, что версия гистограммы представляет собой сгруппированную гистограмму, поэтому я должен иметь вложенность в категории. Другим является то, что у меня нет контроля над исходной формой данных - она ​​исходит от клиента. Я опубликовал обновление вопроса, которое включает ссылку на то, как она должна выглядеть и функционировать.Однако этот вопрос по-прежнему имеет значение, поскольку я использовал два отдельных гнезда данных для достижения результата, что кажется мне неэффективным. – tgerard

+0

@tgerard ваша рабочая версия отлично смотрится! Я добавил редактирование в свой ответ, чтобы объяснить, как можно расширить то, что мне нужно, чтобы получить те дополнительные «функции», которые вы искали. Что касается «неэффективности» вашего подхода, я думаю, что это может потребоваться с организацией ваших исходных данных. Вы можете начать с переформатирования с помощью javascript, а затем последовать этому примеру тем, что у меня есть, но в этот момент, вероятно, имеет смысл придерживаться того, что у вас есть (если только вам не придется многократно бороться с этим неэффективным подходом). –

+0

Да, я просто придерживаюсь своего первоначального подхода. Когда я впервые разместил этот вопрос, я подумал, что ответить на это было бы легко для кого-то с лучшими навыками кодирования, чем у меня, но теперь я пришел к выводу, что это на самом деле довольно сложно, и я не должен бояться иметь два гнезда. Еще раз спасибо за это. – tgerard

Смежные вопросы