Возникли проблемы с осью Y в D3js

#d3.js #charts #data-visualization #bar-chart

Вопрос:

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

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

Моя фотография

И вот мое второе изображение диаграммы, если я загружу меньше данных.

введите описание изображения здесь

Я действительно не понимаю, чего мне здесь не хватает, но я считаю, что это что-то с высотой оси Y и положением стержней y. Не могли бы вы, пожалуйста, помочь мне разобраться в этом?

Вот мой код:

 var margin = { top: 20, right: 30, bottom: 40, left: 90 },
    width = 360 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
    .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   ")");

// Parse the Data
var data2 = d3.json("/Events/BarChart/4").then(function (data) {
    console.log(data);
    // Add X axis
    var x = d3.scaleLinear()
        .domain([0, 5])
        .range([0, width]);
    svg.append("g")
        .attr("transform", "translate(0,"   height   ")")
        .call(d3.axisBottom(x))
        .selectAll("text")
        .attr("transform", "translate(-10,0)rotate(-45)")
        .style("text-anchor", "end")

        ;

    // Y axis
    var y = d3.scaleBand()
        .range([0, height])
        .domain(data.map(function (d) { return d.name; }))

    svg.append("g")
        .call(d3.axisLeft(y))

    //Bars
    svg.selectAll("myRect")
        .data(data)
        .enter()
        .append("rect")
        .attr("x", 4)
        .attr("y", function (d) { return y(d.name)   10; })
        .attr("width", function (d) { return x(d.value); })
        .attr("height", 20)
        .attr("fill", function (d) {
            if (d.value > 1) {
                return "rgb(51, 80, 92)";
            }
            else if (d.value > 1 amp;amp; d.value < 4) {
                return "rgb(118, 161, 179)"
            }
            else {
                return "rgb(171, 209, 224)";
            }
        })
})
 

Ответ №1:

Проблема возникает из — за того, что вы вручную назначаете каждому прямоугольнику высоту 20 пикселей, но задаете диапазон масштаба от 0 до 240 (значение height ). Шкала разделит диапазон на равные сегменты (полосы), по одному для каждого значения в своей области. Если у вас есть только два значения в домене, они будут иметь полосы по 120 пикселей каждая (уменьшенные, если есть заполнение). Нигде шкала «не знает», что вы назначили высоту всего 20 пикселей для каждого бара; в конце концов, вы сказали ей равномерно распределять значения в диапазоне от 0 до 240. Эти противоречивые инструкции объясняют, почему ваши столбики не выровнены по вашей оси.

При использовании шкал d3 вам будет намного проще, если вы будете использовать шкалу как для оси, так и для рисования самих данных (прямые/круги/и т.д.): таким образом, они всегда будут выровнены.

Шкала диапазона d3 предлагает удобный метод: scale.bandwidth() , это возвращает длину/ширину/высоту полосы в шкале: в самом простом случае (без заполнения) это размер диапазона, разделенный на количество различных значений в домене. Мы можем использовать это значение для установки высоты полосы:

 var margin = { top: 20, right: 30, bottom: 40, left: 90 },
    width = 360 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
    .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 = [
  {name: "a", value: 1},
  {name: "b", value: 2}
]

    // Add X axis
    var x = d3.scaleLinear()
        .domain([0, 5])
        .range([0, width]);
    svg.append("g")
        .attr("transform", "translate(0,"   height   ")")
        .call(d3.axisBottom(x))
        .selectAll("text")
        .attr("transform", "translate(-10,0)rotate(-45)")
        .style("text-anchor", "end")

        ;

    // Y axis
    var y = d3.scaleBand()
        .range([0, height])
        .domain(data.map(function (d) { return d.name; }))

    svg.append("g")
        .call(d3.axisLeft(y))

    //Bars
    svg.selectAll("myRect")
        .data(data)
        .enter()
        .append("rect")
        .attr("x", 4)
        .attr("y", function (d) { return y(d.name); })
        .attr("width", function (d) { return x(d.value); })
        .attr("height", y.bandwidth())
        .attr("fill", function (d) {
            if (d.value > 1) {
                return "rgb(51, 80, 92)";
            }
            else if (d.value > 1 amp;amp; d.value < 4) {
                return "rgb(118, 161, 179)"
            }
            else {
                return "rgb(171, 209, 224)";
            }
        }) 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="my_dataviz"></div> 

Я также заметил, что вы добавляете 10 пикселей к значению y каждого бара: вероятно, это было сделано для того, чтобы вручную лучше выровнять бары с несколькими записями данных. Как правило, это вызовет проблемы (если их не исправить вручную): масштаб(значение) и масштаб.полоса пропускания() для y/x и высоты/ширины соответственно создают полосы, центрированные по оси тиков. Если вам нужно заполнение (пространство между столбцами), проще всего установить это с помощью шкалы: scale.padding(number) где number значение от 0 до 1, представляющее часть каждого пустого сегмента:

 var margin = { top: 20, right: 30, bottom: 40, left: 90 },
    width = 360 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
    .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 = [
  {name: "a", value: 1},
  {name: "b", value: 2}
]

    // Add X axis
    var x = d3.scaleLinear()
        .domain([0, 5])
        .range([0, width]);
    svg.append("g")
        .attr("transform", "translate(0,"   height   ")")
        .call(d3.axisBottom(x))
        .selectAll("text")
        .attr("transform", "translate(-10,0)rotate(-45)")
        .style("text-anchor", "end")

        ;

    // Y axis
    var y = d3.scaleBand()
        .range([0, height])
        .padding(0.1)
        .domain(data.map(function (d) { return d.name; }))

    svg.append("g")
        .call(d3.axisLeft(y))

    //Bars
    svg.selectAll("myRect")
        .data(data)
        .enter()
        .append("rect")
        .attr("x", 4)
        .attr("y", function (d) { return y(d.name); })
        .attr("width", function (d) { return x(d.value); })
        .attr("height", y.bandwidth())
        .attr("fill", function (d) {
            if (d.value > 1) {
                return "rgb(51, 80, 92)";
            }
            else if (d.value > 1 amp;amp; d.value < 4) {
                return "rgb(118, 161, 179)"
            }
            else {
                return "rgb(171, 209, 224)";
            }
        }) 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="my_dataviz"></div> 

Но что, если вам не нужны сегменты шириной 120 пикселей? Вы хотите, чтобы ваши полосы всегда были размером 20 пикселей, независимо от того, сколько у вас баров. Ну, мы можем изменить диапазон шкалы, чтобы отразить длину домена:

 var margin = { top: 20, right: 30, bottom: 40, left: 90 },
    width = 360 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
    .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 = [
  {name: "a", value: 1},
  {name: "b", value: 2}
]

    // Add X axis
    var x = d3.scaleLinear()
        .domain([0, 5])
        .range([0, width]);
    svg.append("g")
        .attr("transform", "translate(0,"   data.length*20   ")")
        .call(d3.axisBottom(x))
        .selectAll("text")
        .attr("transform", "translate(-10,0)rotate(-45)")
        .style("text-anchor", "end")

        ;

    // Y axis
    var y = d3.scaleBand()
        .domain(data.map(function (d) { return d.name; }))
        .range([0, data.length*20])
        .padding(0.1);

    svg.append("g")
        .call(d3.axisLeft(y))

    //Bars
    svg.selectAll("myRect")
        .data(data)
        .enter()
        .append("rect")
        .attr("x", 4)
        .attr("y", function (d) { return y(d.name); })
        .attr("width", function (d) { return x(d.value); })
        .attr("height", y.bandwidth())
        .attr("fill", function (d) {
            if (d.value > 1) {
                return "rgb(51, 80, 92)";
            }
            else if (d.value > 1 amp;amp; d.value < 4) {
                return "rgb(118, 161, 179)"
            }
            else {
                return "rgb(171, 209, 224)";
            }
        }) 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="my_dataviz"></div> 

Я также обновил преобразование для оси x, вы также можете продолжить настройку высоты svg для лучшего размера

Комментарии:

1. Ух ты! Большое вам спасибо за всю эту информацию. Очень признателен. Так приятно просыпаться утром и видеть решение своей проблемы, из-за которого ты почти не спал, ха-ха. Еще раз спасибо, приятель.