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
Это сделало галочки идеально согласованными с вертикальными полосами.