react-plotly.js icon indicating copy to clipboard operation
react-plotly.js copied to clipboard

Axes incorrect when updating layout but not data

Open who-knows-who opened this issue 1 year ago • 4 comments

When updating the layout but passing a constant data object (or a new object but with nested constant objects), the traces stay the same but the axes reset to the defaults as if no data is plotted, causing the graph to be incorrect. The data points can also not be hovered over in this state.

Forcing the plot to redraw (e.g. by clicking 'Reset axes') corrects the plot and creating a deep copy of the data object before passing to the Plot component removes the issue.

Example at https://codesandbox.io/s/agitated-babycat-j9hjb8?file=/src/App.js (issue can be seen when changing text in input)

I have been unable to recreate this with pure plotly.js but I am less familiar with that library, so sorry if the issue is there instead.

who-knows-who avatar Apr 04 '23 09:04 who-knows-who

You have to use useState when updating the layout. Adding the code below should no longer automatically adjust the x-axis.

...

  const [layout, setLayout] = React.useState({
    xaxis: {
      title: axisTitle
    }
  });

  React.useEffect(() => {
    setLayout(prevLayout => ({
      ...prevLayout, 
      xaxis: {
        ...prevLayout.xaxis,
        title: axisTitle
      }
    }))
  }, [axisTitle])

...

msimoni18 avatar Apr 04 '23 12:04 msimoni18

You have to use useState when updating the layout. Adding the code below should no longer automatically adjust the x-axis.

React advices against this pattern (updating one state based on another): https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state

who-knows-who avatar Apr 04 '23 13:04 who-knows-who

You can also do it this way if you only want to manage one state.

function App() {
  const [layout, setLayout] = React.useState({
    xaxis: {
      title: "x axis"
    }
  });

  const handleAxisChange = (event) => {
    setLayout((prevLayout) => ({
      ...prevLayout,
      xaxis: {
        ...prevLayout.xaxis,
        title: event.target.value
      }
    }));
  };

  return (
    <div>
      <input value={layout.xaxis.title} onChange={handleAxisChange} />
      <Plot data={data} layout={layout} />
    </div>
  );
}

msimoni18 avatar Apr 04 '23 13:04 msimoni18

Unfortunately the actual use case that lead to this is a bit more complicated (multiple axis titles, switching between 2D/3D so titles are at layout.(x/y)axis.title or layout.scene.(x/y/z)axis.title, wanting to use those strings elsewhere, etc.) so storing the whole layout as state isn't really feasible.

Taking a deep copy of the data object between generation and use is probably the cleanest solution for now

who-knows-who avatar Apr 04 '23 15:04 who-knows-who