Transition числовых меток в bar chart d3

125
10 июня 2021, 09:30

Я изучаю d3.js, и у меня есть вопрос:

Следующий код в d3 в основном рисует bar chart с кнопкой обновления, которая сортирует данные один раз в порядке убывания и один раз в порядке возрастания. Кроме того, появляются числовые метки на столбцах.

Я хотел бы переместить числовую метку из текущего значения в обновленное значение. Например, - если первая полоса имеет числовую метку 20, а новое обновленное значение после сортировки - 100, то я бы хотел, чтобы эта метка переходила с 20 на 100 как (20, 21, ..., 100) во время определенного времени перехода, и наоборот, если исходная метка равна 100, а обновленное значение равно 20, переход идет в порядке убывания, как 100, 99, ..., 20.

const data = [
  {key: 0, value: 50},
  {key: 1, value: 20},
  {key: 2, value: 100},
  {key: 3, value: 30},
  {key: 4, value: 40},
  {key: 5, value: 70}
]
// const dataset = [50, 20, 100, 30, 40]
const svgWidth = 800;
const svgHeight = 400;
const xScale = d3.scaleBand()
  .domain(d3.range(data.length))
  .rangeRound([0, svgWidth])
  .paddingInner(0.1);
const yScale = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.value)])
  .range([0, svgHeight]);

const svg = d3.select('#chart')
  .append('svg')
  .attr('width', svgWidth)
  .attr('height', svgHeight);
let bars = svg.selectAll('rect').data(data, d => d.key);
let labels = svg.selectAll('text').data(data);
bars.enter()
  .append('rect')
  .each(function(d){return this._old = d;})
  .attr('width', xScale.bandwidth)
  .attr('height', d => yScale(d.value))
  .attr('fill', d => `rgb(${d.value}, ${d.value * 2}, ${d.value * 3})`)
  .attr('x', (d, i) => xScale(i))
  .attr('y', d => svgHeight - yScale(d.value))
  .attr('stroke', 'black')
  .attr('stroke-width', 3)
labels.enter()
  .append('text')
  .attr('x', (d, i) => xScale(i) + (xScale.bandwidth() / 2))
  .attr('y', d => svgHeight - yScale(d.value) + 20)
  .attr('font-size', 20)
  .attr('text-anchor', 'middle')
  .attr('fill', 'white')
  .text(d => d.value);
  let asc = false;

d3.select('button').on('click', () => {
  if(!asc){
    data.sort((a,b) => b.value - a.value );
  }else{
    data.sort((a,b) => a.value - b.value );
  };
  asc = !asc;


  bars = svg.selectAll('rect').data(data, d => d.key);
  labels = svg.selectAll('text').data(data);
  bars
    .transition()
    .delay((d, i) => (i * 10))
    .duration(3000)
    .each(function(d){return this._old = d;})
    .attr('x', (d, i) => xScale(i))
    .attr('height', d => yScale(d.value))
    .attr('y', d => svgHeight - yScale(d.value));
  labels
    .transition()
    .delay((d, i) => (i * 10))
    .duration(3000)
    .tween("text", function(d) {
      var that = this;
      var i = d3.interpolate(0, d.value);  // Number(d.percentage.slice(0, -1))
      return function(t) {
          d3.select(that).text(i(t).toFixed(0));
      }
    })
    .attr('y', d => svgHeight - yScale(d.value) + 20);
})

Я знаю, что могу просто перенести числовое значение с помощью bar, но я хотел бы знать, как выполнить переход от текущего числового значения к новому значению обновления в качестве упражнения.

Еще один вопрос, касающийся функции анимации: почему мы присваиваем var, который = this, и выбираем его в возвращаемой функции?

Свободный перевод вопроса Transition the numeric labels in a bar chart от участника @BlackMath.

Answer 1

Вы можете получить текущее значение для каждого текста различными способами:

  • Например, с ванильным JavaScript:

var current = +(this.textContent);

  • Или используя геттер D3:

    var current = +(d3.select(this).text());

Вот ваш код с этим изменением:

const data = [{
    key: 0,
    value: 50
  },
  {
    key: 1,
    value: 20
  },
  {
    key: 2,
    value: 100
  },
  {
    key: 3,
    value: 30
  },
  {
    key: 4,
    value: 40
  },
  {
    key: 5,
    value: 70
  }
]
// const dataset = [50, 20, 100, 30, 40]
const svgWidth = 800;
const svgHeight = 400;
const xScale = d3.scaleBand()
  .domain(d3.range(data.length))
  .rangeRound([0, svgWidth])
  .paddingInner(0.1);
const yScale = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.value)])
  .range([0, svgHeight]);

const svg = d3.select('body')
  .append('svg')
  .attr('width', svgWidth)
  .attr('height', svgHeight);
let bars = svg.selectAll('rect').data(data, d => d.key);
let labels = svg.selectAll('text').data(data);
bars.enter()
  .append('rect')
  .each(function(d) {
    return this._old = d;
  })
  .attr('width', xScale.bandwidth)
  .attr('height', d => yScale(d.value))
  .attr('fill', d => `rgb(${d.value}, ${d.value * 2}, ${d.value * 3})`)
  .attr('x', (d, i) => xScale(i))
  .attr('y', d => svgHeight - yScale(d.value))
  .attr('stroke', 'black')
  .attr('stroke-width', 3)
labels.enter()
  .append('text')
  .attr('x', (d, i) => xScale(i) + (xScale.bandwidth() / 2))
  .attr('y', d => svgHeight - yScale(d.value) + 20)
  .attr('font-size', 20)
  .attr('text-anchor', 'middle')
  .attr('fill', 'white')
  .text(d => d.value);
let asc = false;

d3.select('button').on('click', () => {
  if (!asc) {
    data.sort((a, b) => b.value - a.value);
  } else {
    data.sort((a, b) => a.value - b.value);
  };
  asc = !asc;
  bars = svg.selectAll('rect').data(data, d => d.key);
  labels = svg.selectAll('text').data(data);
  bars
    .transition()
    .delay((d, i) => (i * 10))
    .duration(3000)
    .each(function(d) {
      return this._old = d;
    })
    .attr('x', (d, i) => xScale(i))
    .attr('height', d => yScale(d.value))
    .attr('y', d => svgHeight - yScale(d.value));
  labels
    .transition()
    .delay((d, i) => (i * 10))
    .duration(3000)
    .tween("text", function(d) {
      var current = +(d3.select(this).text());
      var that = this;
      var i = d3.interpolate(current, d.value); // Number(d.percentage.slice(0, -1))
      return function(t) {
        d3.select(that).text(i(t).toFixed(0));
      }
    })
    .attr('y', d => svgHeight - yScale(d.value) + 20);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<button>Click</button>
<br>

Свободный перевод ответа от участника @Gerardo Furtado.

READ ALSO
Node js сохранение полученного zip архива

Node js сохранение полученного zip архива

С клиента получаю архив в base64, перевожу в utf, сохраняю, но архив оказывается битымОшибка и код ниже

89
Как связать добавляемые метки линиями?

Как связать добавляемые метки линиями?

При нажатии кнопки в центре карты появляется метка, нужно, чтобы новые метки были соединены с последней добавленной и при этом их все можно...

98
Почему не работает event.preventDefault()

Почему не работает event.preventDefault()

Почему не работает eventpreventDefault();?

85
Нужна помощь по api kodexplorer

Нужна помощь по api kodexplorer

подскажите пожалуйста каким образом из javascript закрыть собственное окно в kodexplorer? Какую функцию top фрейма нужно вызвать и c какими параметрами?...

100