RangeRoundBands outerPadding в гистограмме слишком большой

Я новичок в D3.js и имею проблему с моей вертикальной гистограммой. По какой-то причине расстояние между осью и полосками слишком велико, когда я использую rangeRoundBands для масштабирования. В API это объясняется следующим образом:

enter image description here

Таким образом, проблема заключается в том, что проблема заключается в том, что это внешняя оболочка. Но установка внешнего пакета на ноль не помогает. Однако, когда я использую диапазоны диапазонов, проблема исчезает, и бары расположены правильно, прямо под осью. Но тогда я получу эти неприятные эффекты сглаживания, так что это не вариант. Вот мой код:

var margin = {top: 40, right: 40, bottom: 20, left: 20},
    width = 900 - margin.left - margin.right,
            height = x - margin.top - margin.bottom;

    var x = d3.scale.linear().range([0, width]);

    var y = d3.scale.ordinal().rangeRoundBands([0, height], .15, 0);

    var xAxis = d3.svg.axis()
            .scale(x)
            .orient("top");

    var xAxis2 = d3.svg.axis()
            .scale(x)
            .orient("bottom");

    var xAxis3 = d3.svg.axis()
            .scale(x)
            .orient("bottom")
            .tickSize(-height, 0, 0)
            .tickFormat("");

    var svg = d3.select("#plotContainer").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 + ")");


    x.domain(d3.extent(data, function(d) {
        return d.size;
    })).nice();
    y.domain(data.map(function(d) {
        return d.name;
    }));

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

    svg.selectAll(".bar")
            .data(data)
            .enter().append("rect")
            .attr("class", function(d) {
                return d.size < 0 ? "bar negative" : "bar positive";
            })
            .attr("x", function(d) {
                return x(Math.min(0, d.size));
            })
            .attr("y", function(d) {
                return y(d.name);
            })
            .attr("width", function(d) {
                return Math.abs(x(d.size) - x(0));
            })
            .attr("height", y.rangeBand())
            .append("title")
            .text(function(d) {
                return "This value is " + d.name;
            });
    ;

    svg.selectAll(".bar.positive")
            .style("fill", "steelblue")
            .on("mouseover", function(d) {
                d3.select(this).style("fill", "yellow");
            })
            .on("mouseout", function(d) {
                d3.select(this).style("fill", "steelblue");
            });

    svg.selectAll(".bar.negative")
            .style("fill", "brown")
            .on("mouseover", function(d) {
                d3.select(this).style("fill", "yellow");
            })
            .on("mouseout", function(d) {
                d3.select(this).style("fill", "brown");
            });

    svg.selectAll(".axis")
            .style("fill", "none")
            .style("shape-rendering", "crispEdges")
            .style("stroke", "#000")
            .style("font", "10px sans-serif");

    svg.selectAll(".grid")
            .style("fill", "none")
            .style("stroke", "lightgrey")
            .style("opacity", "0.7");

    svg.selectAll(".grid.path")
            .style("stroke-width", "0");

EDIT: Пожалуйста, взгляните на эту скрипку: http://jsfiddle.net/GUYZk/9/

Моя проблема воспроизводится там. Вы не можете изменить внешний Паддинг с помощью rangeRoundBands, тогда как диапазонBands ведет себя нормально.

Ответ 1

TL; DR: это следствие математики. Чтобы обойти, используйте rangeBands, чтобы выложить бары, и используйте shape-rendering: crispEdges в CSS, чтобы выровнять их с границами пикселей.


Полное объяснение:

Поскольку функция rangeRoundBands должна равномерно распределять столбцы по предоставленному диапазону пикселей, он также должен содержать целое число rangeBand, он использует Math.floor для измельчения дробного бита каждого последующего бара.

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

Поскольку пиксель фракции каждого округленного столбца находится на интервале (0, 1), дополнительные пиксели, заимствованные на каждом конце, будут охватывать около 1/4 от количества баров данных. С 10 барами 2-3 лишних пикселя никогда не будут замечены, но если у вас есть 100 или более, дополнительные 25+ пикселей станут намного заметнее.

Одно из возможных решений, которое работает в Chrome для svg:rect: используйте rangeBands для размещения, но затем примените shape-rendering: crispEdges как стиль CSS для ваши дорожки/прямоугольники.

В этом случае нагрузка на SVG-рендерер будет вытеснена каждым столбцом на границу пикселя, но они будут более равномерно распределены в целом, с периодической дисперсией в промежутке для учета ошибки во всей диаграмме.

Лично я использую shape-rendering: optimizeSpeed и позволяю агенту рендеринга делать любые компромиссы, необходимые для быстрого рендеринга позиций (потенциально дробных).

Ответ 2

Я знаю, что это старо, но я пришел к этому ответу с той же проблемой; Ответ Бена предлагает другой простой (но, вероятно, более медленный) обходной путь; вы можете исправить отступы, сдвинув все значения x слева на первое значение в вычисленном диапазоне.

Пример:

// set your desired gap between y-axis and first bar
var yGap = 5;

// create your scale
var xScale = d3.scale.ordinal()
  .domain(dataArray)
  .rangeRoundBands([0, width], .1);

// obtain the *computed* left edge of the first bar
var leftOuter = xScale.range()[0];

// when setting the x attribute of your rects:
  .attr("x", function(d) { return xScale(d) - leftOuter + yGap; });

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

Я надеюсь, что кто-то найдет это полезным!

Ответ 3

У меня такая же проблема, и независимо от того, что я использовал для внутренней прокладки или внешней прокладки, я просто не мог заставить тики выровнять по центру своих вертикальных баров на оси X. Поэтому я не использовал внутреннее или внешнее дополнение. Я использовал свое собственное дополнение, скажем 0,1.

для прямоугольника, я устанавливаю ширину как

width: (1.0 - padding) * xScale.rangeBand()

для x я просто добавляю половину отступов, как это.

x: padding * xScale.rangeBand() / 2

Это сделало галочки идеально согласованными с вертикальными полосами.