Переход на гистограмму с отрицательными значениями для ширины

78
12

Я создаю горизонтальную гистограмму, используя d3. И я использую анимацию для "роста" диаграммы при запуске. Вот код.

// Create the svg element
d3.select("#chart-area")
.append("svg")
.attr("height", 800)
.attr("width", 800);

.data(dataValues) // This data is previously prepared
.enter().append("rect")
.style("fill", "blue")

.attr("x", function () { return xScale(0); }) // xScale is defined earlier
.attr("y", function (d) { return yScale(d); }) // yScale is defined earlier

.attr("height", yScale.bandwidth()) // yScale is defined earlier

// Initial value of "width" (before animation)
.attr("width", 0)

// Start of animation transition
.transition()
.duration(5000) // 5 seconds

.ease (d3.easeLinear);

// Final value of "width" (after animation)
.attr("width", function(d) { return Math.abs(xScale(d) - xScale(0)); })

Вышеприведенный код будет работать без каких-либо проблем, и линии будут развиваться по назначению с 0 до любой ширины в течение 5 секунд.

Теперь, если мы изменим линию ослабления на следующую

    // This line changed
.ease (d3.easeElasticIn);

Тогда легкость попытается взять ширину до отрицательного значения, прежде чем перейти к окончательному положительному значению. Как вы можете видеть здесь, d3.easeElasticIn возвращает отрицательные значения с течением времени, а затем обратно к положительному, что приводит к отрицательному отрицанию в определенных точках анимации. Таким образом, бары не отображаются должным образом (поскольку спецификации SVG указывают, что если ширина отрицательная, то используйте 0)

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

Благодарю.

спросил(а) 2021-01-25T14:38:26+03:00 4 месяца, 4 недели назад
1
Решение
76

Как вы уже знаете, использование d3.easeElasticIn в вашем конкретном коде создаст отрицательные значения для ширины прямоугольников, что недопустимо.

Эта базовая демонстрация воспроизводит проблему, консоль (консоль вашего браузера, а не консоль фрагмента) заполняется сообщениями об ошибках, например:

Ошибка: недопустимое отрицательное значение для ширины атрибута = "-85.90933910798789"

Посмотри:

const svg = d3.select("svg");

const margin = 50;

const line = svg.append("line")
.attr("x1", margin)
.attr("x2", margin)
.attr("y1", 0)
.attr("y2", 150)
.style("stroke", "black")

const data = d3.range(10).map(function(d) {
return {
y: "bar" + d,
x: Math.random()
}
});

const yScale = d3.scaleBand()
.domain(data.map(function(d) {
return d.y
}))
.range([0, 150])
.padding(0.2);

const xScale = d3.scaleLinear()
.range([margin, 300]);

const bars = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("x", margin)
.attr("width", 0)
.style("fill", "steelblue")
.attr("y", function(d) {
return yScale(d.y)
})
.attr("height", yScale.bandwidth())
.transition()
.duration(2000)
.ease(d3.easeElasticIn)
.attr("width", function(d) {
return xScale(d.x) - margin
})

<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>

Итак, какое решение?

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

Для этого нам нужно будет использовать attrTween вместо attr в выборе перехода.

Как это:

.attrTween("width", function(d) {
return function(t){
return Math.abs(xScale(d.x) * t);
};
})
.attrTween("x", function(d) {
return function(t){
return xScale(d.x) * t < 0 ? margin + xScale(d.x) * t : margin;
};
})

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

И вот демо:

const svg = d3.select("svg");

const margin = 100;

const line = svg.append("line")
.attr("x1", margin)
.attr("x2", margin)
.attr("y1", 0)
.attr("y2", 150)
.style("stroke", "black")

const data = d3.range(10).map(function(d) {
return {
y: "bar" + d,
x: Math.random()
}
});

const yScale = d3.scaleBand()
.domain(data.map(function(d) {
return d.y
}))
.range([0, 150])
.padding(0.2);

const xScale = d3.scaleLinear()
.range([0, 300 - margin]);

const bars = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("x", margin)
.attr("width", 0)
.style("fill", "steelblue")
.attr("y", function(d) {
return yScale(d.y)
})
.attr("height", yScale.bandwidth())
.transition()
.duration(2000)
.ease(d3.easeElasticIn)
.attrTween("width", function(d) {
return function(t) {
return Math.abs(xScale(d.x) * t);
};
})
.attrTween("x", function(d) {
return function(t) {
return xScale(d.x) * t < 0 ? margin + xScale(d.x) * t : margin;
};
})

<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>

ответил(а) 2021-01-25T14:38:26+03:00 4 месяца, 4 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема