Переход гистограммы с отрицательными значениями ширины - PullRequest
0 голосов
/ 22 сентября 2018

Я создаю горизонтальную гистограмму, используя 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)

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

Спасибо.

1 Ответ

0 голосов
/ 22 сентября 2018

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

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

Ошибка: недопустимое отрицательное значение для атрибута width = "- 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>
...