How to properly transition when reflecting an element with d3?

49 Views Asked by At

I have a path element that I am updating with new data. It is then repositioned and also reflected. All of that is working correctly. The part that is not working correctly is the transition. Due to the way I am reflecting (using scale and translate), it moves past the correct position before returning.

How can I transition from the initial x-axis position to the new x-axis position without moving past the new position?

jsFiddle

HTML:

<button id="reflect">reflect</button>
<div id="container"></div>

JS:

import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";

const width = 500;
const height = 200;

const x = d3.scaleLinear()
  .domain([0, 100])
  .range([0, width]);

const y = d3.scaleLinear()
  .domain([0, 50])
  .range([height, 0]);

const svg = d3.create("svg")
  .attr("width", width)
  .attr("height", height);

svg.append("g")
  .attr("transform", `translate(0,${height})`)
  .call(d3.axisBottom(x));

svg.append("g")
  .call(d3.axisLeft(y));

const data = [{
  x: 60,
  y: 20
}];
const reflectedData = [{
  x: 30,
  y: 20
}];

svg.selectAll('arrow')
  .data(data)
  .join('path')
  .attr('class', 'arrow')
  .attr('d', 'M0,0 L80,0 L80, 50z')

d3.select('#reflect')
  .on('click', () => {
    svg.selectAll('.arrow')
      .data(reflectedData)
    updateArrow(2000)
  })

updateArrow(0);

function updateArrow(duration) {
  const midpoint = x.domain()[1] / 2

  svg.selectAll('.arrow')
    .transition()
    .duration(duration)
    .style('scale', d => d.x < midpoint ? '-1 1' : '1 1')
    .style('transform', d => {
      const translateX = d.x < midpoint ? -x(d.x) : x(d.x);
      return `translate(${translateX}px, ${y(d.y)}px)`;
    })
}

container.append(svg.node());
1

There are 1 best solutions below

0
BallpointBen On BEST ANSWER

Two problems:

  1. transform doesn't really play nice with other transform-like attributes like scale. (At least, it's very hard to reason about.) Much easier to just do everything in transform.
  2. The translation shouldn't change depending on whether d.x < midpoint; only the scale should.
function updateArrow(duration) {
  const midpoint = x.domain()[1] / 2

  svg.selectAll('.arrow')
    .transition()
    .duration(duration)
    .style('transform', d =>
      `translate(${x(d.x)}px, ${y(d.y)}px) scaleX(${d.x < midpoint ? -1 : 1})`
    )
}

The triangle flips over the vertical as it moves from right to left.