Не удается создать гистограмму с накоплением? - PullRequest
2 голосов
/ 05 апреля 2019

Я создаю столбчатую диаграмму с накоплением, используя диаграммы d3.У меня есть кнопка в передней части.При нажатии кнопки должна отображаться диаграмма. При нажатии на кнопку я получаю следующую ошибку.

Ошибка: атрибут y: ожидаемая длина, «NaN».

Ошибка: высота атрибута: ожидаемая длина,«NaN».

Ошибка: ширина атрибута: ожидаемая длина, «10,900».

import * as d3 from 'd3';

export class stackedbarchart  {

    sbar(box_id){

      d3.select(box_id)
      .select("svg")
      .remove();

      var margin = { top: 20, right: 160, bottom: 35, left: 30 };

      var width = 1100 - margin.left - margin.right,
          height = 500 - margin.top - margin.bottom;

      var svg = d3.select("body")
        .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

      var data = [
        { country:"India", sales:100, profit:100, loss:10 },
        { country:"US", sales:100, profit:80, loss:40 },
        { country:"aus", sales:100, profit:70, loss:30 }
      ];

      var stack = d3.stack()
      .keys(["sales","profit","loss"])
      .order(d3.stackOrderNone)
      .offset(d3.stackOffsetNone);

      var series = stack(data);

      var x = d3.scaleOrdinal()
        .domain(series[0].map(function(d) {
          console.log(d)
           return d.x; }))
        .range([10, width-10], 0.02);

      var y = d3.scaleLinear()
        .domain([0, d3.max(series, function(d) {   
          return d3.max(d, function(d) { 
            return d.y0 + d.y; });  })])
        .range([height, 0]);

      var colors = ["b33040", "#d25c4d", "#f2b447", "#d9d574"];

      var yAxis = d3.axisLeft(y)
        .ticks(5)
        .tickSize(-width, 0, 0)
        .tickFormat( function(d) { return d } );

      var xAxis = d3.axisBottom(x)
      console.log(xAxis)

      svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

      svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

      var groups = svg.selectAll("g.cost")
        .data(series)
        .enter().append("g")
        .attr("class", "cost")
        .style("fill", function(d, i) { return colors[i]; });

      var rect = groups.selectAll("rect")    
        .data(function(d) { return d; })
        .enter()
        .append("rect")
        .attr("x", function(d) { return x(d.x); })
        .attr("y", function(d) { return y(d.y0 + d.y); })
        .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
        .attr("width", x.range())
        .on("mouseover", function() { tooltip.style("display", null); })
        .on("mouseout", function() { tooltip.style("display", "none"); })
        .on("mousemove", function(d) {
          var xPosition = d3.mouse(this)[0] - 15;
          var yPosition = d3.mouse(this)[1] - 25;
          tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
          tooltip.select("text").text(d.y);
        });

      var legend = svg.selectAll(".legend")
        .data(colors)
        .enter().append("g")
        .attr("class", "legend")
        .attr("transform", function(d, i) { return "translate(30," + i * 19 + ")"; });

      legend.append("rect")
        .attr("x", width - 18)
        .attr("width", 18)
        .attr("height", 18)
        .style("fill", function(d, i) {return colors.slice().reverse()[i];});

      legend.append("text")
        .attr("x", width + 5)
        .attr("y", 9)
        .attr("dy", ".35em")
        .style("text-anchor", "start")
        .text(function(d, i) { 
          switch (i) {
            case 0: return "sales";
            case 1: return "profit";
            case 2: return "loss"; 
          }
        });

      var tooltip = svg.append("g")
        .attr("class", "tooltip")
        .style("display", "none");

      tooltip.append("rect")
        .attr("width", 30)
        .attr("height", 20)
        .attr("fill", "white")
        .style("opacity", 0.5);

      tooltip.append("text")
        .attr("x", 15)
        .attr("dy", "1.2em")
        .style("text-anchor", "middle")
        .attr("font-size", "12px")
        .attr("font-weight", "bold") ;

    }
}

Любая помощь или подсказка в правильном направлении будет принята с благодарностью.

1 Ответ

0 голосов
/ 05 апреля 2019

Изменения в D3v4

В d3v4 были внесены изменения, которые могут нарушить график d3v3. Стеки в d3v3 имели x аксессоров и использовали y0 и y для представления начальных и конечных значений для каждого сегмента стекового графика. Результат выглядит примерно так:

 .append("rect")
  .attr("x", function(d) { return x(d.x); })
  .attr("y", function(d) { return y(d.y0 + d.y); })
  .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })

Этот подход не работает с d3v4 +, но, похоже, он используется вами. На первый взгляд кажется, что ваш код может быть основан на этом v3 коде.

Стеки в d3v4 +

Давайте посмотрим, что d3.stack производит в d3v4 +.

У нас есть входные данные:

var data = [
  { country:"India", sales:100, profit:100, loss:10 },
  { country:"US", sales:100, profit:80, loss:40 },
  { country:"aus", sales:100, profit:70, loss:30 }
];

И генератор стека:

var stack = d3.stack()
  .keys(["sales","profit","loss"])
  .order(d3.stackOrderNone)     // default value, does not need to be specified.
  .offset(d3.stackOffsetNone)   // default value, does not need to be specified

Когда мы передаем данные в генератор стека, мы получаем:

[/*  India     US        AUS   */
 [[0,  100],[0,  100],[0,  100]],  // Sales
 [[100,200],[100,180],[100,170]],  // Profit
 [[200,210],[180,220],[170,200]]   // Loss 
]

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

Массивы, которые содержат все значения для данного ключа (например, [[0,100],[0,100],[0,100]] для продаж), также содержат свойство "ключ", которое содержит имя этого ключа.

Каждый из массивов, представляющих один сегмент (например, [0,100]), имеет свойство data, которое является элементом в исходном массиве данных (например, {"country": "India","sales": 100,"profit": 100,"loss": 10}).

Количество элементов в массиве стека (количество строк) не равно количеству серий (в данном случае страны), но равно количеству ключей.

Создание весов

Первая проблема, с которой вы сталкиваетесь, - как вы определяете шкалы:

var x = d3.scaleOrdinal()
  .domain(series[0].map(function(d) { return d.x; }))
  .range([10, width-10], 0.02);

var y = d3.scaleLinear()
  .domain([0, d3.max(series, function(d) { return d3.max(d, function(d) { return d.y0 + d.y; });  })])
  .range([height, 0]);

Поскольку стек не создает никаких свойств y0, y или x, они не будут работать.

Масштаб x должен быть масштабом полосы (гистограммы обычно используют масштаб полосы с d3), и мы можем просто использовать исходный массив данных для создания домена:

var x = d3.scaleBand()  // band scale
   .domain(data.map(function(d) {
         return d.country; }))  // return the country.
   .range([10, width-10]); 

Шкала y должна иметь доступ к d [0] и d [1], а не к d.y0 и d.y:

var y = d3.scaleLinear()
   .domain([0, d3.max(series, function(d) {   
       return d3.max(d, function(d) { 
            return d[0] + d[1]; });  })]) // access stack values (not d.y, d.y0)
   .range([height, 0]);  

Добавление прямоугольников

Теперь давайте перейдем к тому месту, куда добавляем прямоугольники:

    .append("rect")
    .attr("x", function(d) { return x(d.x); })
    .attr("y", function(d) { return y(d.y0 + d.y); })
    .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
    .attr("width", x.range())

Поскольку d.x не определено, мы можем использовать x(d.data.country). Там, где появляются d.y0 и d.y, мы должны заменить d[0] и d[1]. Наконец, x.range() - это почти полная ширина графика, вместо этого мы можем использовать x.bandwidth(), то есть ширину одного столбца.

Ниже приведен ваш код (начиная с того, где вы определяете маржу) с этими обновлениями:

var margin = { top: 20, right: 160, bottom: 35, left: 30 };

var width = 500 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;

var svg = d3.select("body")
   .append("svg")
   .attr("width", width + margin.left + margin.right)
   .attr("height", height + margin.top + margin.bottom)
   .append("g")
   .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var data = [
  { country:"India", sales:100, profit:100, loss:10 },
  { country:"US", sales:100, profit:80, loss:40 },
  { country:"Aus", sales:100, profit:70, loss:30 },
  { country:"NZ", sales:100, profit:70, loss:30 }
];

var stack = d3.stack()
   .keys(["sales","profit","loss"])
   .order(d3.stackOrderNone)
   .offset(d3.stackOffsetNone);

var series = stack(data);


var x = d3.scaleBand()  // band scale
   .domain(data.map(function(d) {
         return d.country; }))  // return the country.
   .range([10, width-10])
   .padding(0.1); // space between (value between 0 and 1).

var y = d3.scaleLinear()
   .domain([0, d3.max(series, function(d) {   
       return d3.max(d, function(d) { 
            return d[0] + d[1]; });  })]) // access stack values (not d.y, d.y0)
   .range([height, 0]);  

// color scale:
var colors = d3.scaleOrdinal()
  .range(["b33040", "#d25c4d", "#f2b447", "#d9d574"])
  .domain(series.map(function(d) { return d.key; }));

var yAxis = d3.axisLeft(y)
   .ticks(5)
   .tickSize(-width, 0, 0)
   .tickFormat( function(d) { return d } );

var xAxis = d3.axisBottom(x)
     
svg.append("g")
   .attr("class", "y axis")
   .call(yAxis);

svg.append("g")
  .attr("class", "x axis")
  .attr("transform", "translate(0," + height + ")")
  .call(xAxis);

var groups = svg.selectAll("g.cost")
  .data(series)
  .enter().append("g")
  .attr("class", "cost")
  .style("fill", function(d) { return colors(d); });


// More changes:
var rect = groups.selectAll("rect")    
 .data(function(d) { return d; })
 .enter()
 .append("rect")
 .attr("x", function(d) { return x(d.data.country); })  // Access country.
 .attr("y", function(d) { return y(d[0] + d[1]); })
 .attr("height", function(d) { return y(d[0]) - y(d[0] + d[1]); })
 .attr("width", x.bandwidth())

// Minor changes:
var legend = svg.selectAll(".legend")
 .data(series)
 .enter().append("g")
 .attr("class", "legend")
 .attr("transform", function(d, i) { return "translate(30," + i * 19 + ")"; });

legend.append("rect")
  .attr("x", width - 18)
  .attr("width", 18)
  .attr("height", 18)
  .style("fill", function(d) {
      return colors(d.key);
  });

legend.append("text")
  .attr("x", width + 5)
  .attr("y", 9)
  .attr("dy", ".35em")
  .style("text-anchor", "start")
  .text(function(d) { return d.key; })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Для упрощения фрагмента я убрал взаимодействие с мышью

Создание легенды

Это можно немного упростить, это не является вопросом вопроса, тем не менее, я включил его здесь вместе с цветовой шкалой.

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