question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

Performant way to update scales?

See original GitHub issue

Iā€™m trying to make a chart where the scaleā€™s domain is adjustable through a slider (input[type=ā€œrangeā€]) beneath the chart. (Like this, for example.)

The problem that I am running into is that using setState is not performant, as I would like updates to happen every few ms as the slider is dragged, and not just about once per second.

With setState, Iā€™d do something like this:

onRangeChange(e) {
  const newMaxDays = // calculation
  this.setState({ maxDays: newMaxDays })
}

render() {
  data = someData()

  const xScale = scalePower({
    range: [0, width],
    domain: [0, this.state.maxDays],
    exponent: 0.5
  })

  return (
    <div>
      <svg>
        <LinePath
          data={data}
          x={(d) => xScale(d.index)}
          y={(d) => yScale(d.price)}
        />
      </svg>
      
      <input
        type="range"
        min={0}
        max={100}
        defaultValue={50}
        onChange={(e) => this.onRangeChange(e)}
      />
    </div>
  )

This example ^ is slow and laggy. As the user drags the range slider, the chart scale updates, but it is very choppy.

The more performant alternative is to use the innerRef property on the line path so I can access it directly, and then use native d3 logic to set the scale. But Iā€™m having trouble figuring out how to do this.

constructor(props) {
  this.lineRef = React.createRef();
  this.xScale = null
  this.yScale = null
}

onRangeChange(e) {
  const newMaxDays = // calculation

  // TODO: apply the updated xScale to the view... but how??
  // The following doesn't work...

  this.xScale.domain([0, newMaxDays])

  const line = d3.line()
    .x(d => this.xScale(d.index))
    .y(d => this.yScale(d.price))

  d3.select(this.lineRef.current)
    .attr('d', line)
}

render() {
  data = someData()

  this.xScale = scalePower({
    range: [0, width],
    domain: [0, maxDays],
    exponent: 0.5
  })

  this.yScale = ...

  return (
    <div>
      <svg>
        <LinePath
          innerRef={this.lineRef}
          data={data}
          x={(d) => this.xScale(d.index)}
          y={(d) => this.yScale(d.price)}
        />
      </svg>
      
      <input
        type="range"
        min={0}
        max={100}
        defaultValue={50}
        onChange={(e) => this.onRangeChange(e)}
      />
    </div>
  )

Any tips or is there a different preferred way to do this?

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:6

github_iconTop GitHub Comments

1reaction
williastercommented, Jan 24, 2020

Hi @cilphex šŸ‘‹ thanks for checking out vx. Perf in react land can indeed be a tricky thing. Your solution does mix mental models of d3 vs react manipulating the dom, which is why we created vx so I hope we can get a ā€œpure vx/reactā€ solution šŸ˜„ (and I thiiiink that your approach might result in subtle bugs because once you modify the d attribute of the ref, the dom is actually different from what react thinks it is).

Iā€™m curious if a ā€œpure reactā€ version like the following would work, basically you are using the children render function for full control over the LinePath rendering and to get access to the line/path generator, and only updating the scales + domain when needed (note I havenā€™t tested this, just wrote it up so might be errors I didnā€™t catch). It may still re-initialize the line, but might be worth a shot to try and leverage the children render function override.

function MyLine({ width, height, data }) {
  const [maxDays, setMaxDays] = useState(/** initialMaxDays */);
  
  // recompute scales only if chart dims or data change
  const yScale = useMemo(() => /** compute yScale */, [data, height]);
  
  const xScale = useMemo(() => scalePower({
    range: [0, width],
    domain: [0, maxDays],
    exponent: 0.5
  }), [data, width]);

  useEffect(() => {
    // update the x domain when maxDays changes
    xScale.domain([0, maxDays])
  }, [maxDays]);

  return (
    <>
      <input {...} onChange={() => setMaxDays(...)} />
      <svg {...}>
        <LinePath>
          {({ path }) => (
            // get an empty default path, override x and y
            path.x(d => xScale(d.index));
            path.y(d => yScale(d.price));

            return (
              <path d={path(data) || ''} {...} />
            );
          )}
        </LinePath>
      </svg>
    </>
  );
}
0reactions
cilphexcommented, Jan 31, 2020

@williaster I think LinePath.children is missing from the documentation?

Also curious how you would apply this solution to the Grid component. It also has no children property according to the documentation. (But now Iā€™m not sure if thatā€™s because the documentation is lacking or if it really doesnā€™t.)

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to design a system to scale to your first 100 million users
Vertical scaling is a good option for small systems and can afford the hardware upgrade but it also comes with serious limitations asĀ ......
Read more >
Smartsheet Scales: Larger Sheets and Faster Performance
As your organization grows, you need your systems and processes to scale with you. Smartsheet is built to grow with you.
Read more >
Upgrade Your Truck Scale for Better Performance
An existing truck scale can be upgraded to work like new using the POWERCELL PDX load cell system. Traditional truck scales often fall...
Read more >
How do I update my Performance Beef App?
Select the Performance Beef app. To update the app, click the gray and blue Update button to the right of the Performance Beef...
Read more >
Creating Performance Rating Scales: Action Plan & 6 Tips
How should you define performance rating scales? ... status updates, for example, quarterly reviews, performance logs, and ongoing coaching.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found