plotly / react-plotly.js

A plotly.js React component from Plotly 📈
MIT License
1.01k stars 135 forks source link

Axes incorrect when updating layout but not data #316

Open who-knows-who opened 1 year ago

who-knows-who commented 1 year ago

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.

msimoni18 commented 1 year ago

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])

...
who-knows-who commented 1 year ago

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

msimoni18 commented 1 year ago

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>
  );
}
who-knows-who commented 1 year ago

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