nivo icon indicating copy to clipboard operation
nivo copied to clipboard

ResponsiveLine strange refresh when updating data

Open nigellima opened this issue 4 years ago • 14 comments

Describe/explain the bug I have a Chart component for the ResponsiveLine which receives some parameters and data series. I'm calculating the ticks accordingly to the length of my series and the dates (for example, dynamically saying like every 7 days, every 2 months, and so on ).

I'm not remounting the component when the data changes. I'm just passing the updates settings to the props, because I wanted it to animate when the data changes.

However, it seems to have a strange behavious when the data length is greater than 40 data points. When it's less than that, the component refreshes and animate smoothly, but if I pass something like 2 series of 42 and 50 points the animation crashes, the app slows down, the labels start to overlap, like in this example gif:

chart-refresh

On the other hand, if I force the component to re-mount the problem does not happen (but then I dont have the animation)

chart-refresh2

Expected behavior Chart animation occurs normally

Desktop (please complete the following information):

  • OS: MacOS 10.15.4
  • Browser Chrome, Firefox...
  • Version: "@nivo/line": "^0.61.1",

Additional context This is my component

<ResponsiveLine
      data={data as Serie[]}
      margin={{
        top: 20, right: 30, bottom: 80, left: 50,
      }}
      xScale={{
        type: 'time', format: '%Y-%m-%d', precision: 'day', useUTC: false,
      }}
      yScale={{
        type: 'linear', min: 0, max: maxYScale, stacked: false, reverse: false,
      }}
      tooltip={LineTooltip}
      axisBottom={{
        orient: 'bottom',
        renderTick: LineDateTick,
        tickValues: xScaleTickValues,
      }}
      axisLeft={{
        orient: 'left',
        tickSize: 0,
        tickPadding: 15,
        tickRotation: 0,
        tickValues,
        legend: '',
        legendOffset: -40,
        legendPosition: 'middle',
        format: (value): string => formatLeftAxis(value, divisor, unit),
      }}
      colors={{ datum: 'color' }}
      pointSize={5}
      pointColor="#FFFF"
      pointBorderWidth={2}
      pointBorderColor={{ from: 'serieColor' }}
      pointLabelYOffset={-15}
      enableSlices={false}
      useMesh
      onMouseMove={handleMouseMove}
      crosshairType="cross"
      motionStiffness={280}
      legends={enableLegends ? [{
        anchor: 'bottom',
        direction: 'row',
        justify: false,
        translateX: 20,
        translateY: 75,
        itemsSpacing: 0,
        itemDirection: 'left-to-right',
        itemWidth: 190,
        itemHeight: 30,
        itemOpacity: 0.75,
        symbolSize: 12,
        symbolShape: 'circle',
        symbolBorderColor: 'rgba(0, 0, 0, .5)',
      }] : []}
      theme={{
        axis: {
          domain: {
            line: {
              stroke: '#F3F3F3',
              strokeWidth: 1,
            },
          },
        },
        grid: {
          line: {
            stroke: '#FFFF',
            strokeWidth: 1,
          },
        },
      }}
    />

In case it is something not possible to fix, is there any kind of "intro" animation for the chart once it gets mounted?

nigellima avatar Jun 19 '20 02:06 nigellima

@nigellima, to be able to transition between each dataset, we have to preserve some kind of ID for the dots, this ID is computed using the x/y coordinates, I think what's happening there is that some points keep the same ID, so they're not being updated correctly, the scale changes, but not the ID.

plouc avatar Jun 19 '20 21:06 plouc

@nigellima, to be able to transition between each dataset, we have to preserve some kind of ID for the dots, this ID is computed using the x/y coordinates, I think what's happening there is that some points keep the same ID, so they're not being updated correctly, the scale changes, but not the ID.

Thanks for replying @plouc

In that case, what could be the workaround?

nigellima avatar Jun 19 '20 21:06 nigellima

I need to have a look at it and reproduce the issue, I'm not sure what would be the best to preserve transitions while having correct target values, probably need to experiment.

plouc avatar Jun 19 '20 21:06 plouc

I need to have a look at it and reproduce the issue, I'm not sure what would be the best to preserve transitions while having correct target values, probably need to experiment.

Thank you very much @plouc

In the meantime, is there any kind of intro animation? Like, instead of keeping the chart component alive and just update the series props, I could remount every time the series changes and then have an animation at the beginning

nigellima avatar Jun 19 '20 22:06 nigellima

@plouc Hello. How are you? Do you have any updates on this issue? Or, could you point me to where this issue is possibly present so I could help to fix this?

nigellima avatar Aug 24 '20 20:08 nigellima

Some updates on this?

luizfelipelaviola avatar Jan 28 '21 17:01 luizfelipelaviola

I am also looking for an update on this, I've disabled the animations on my graphs for now.

johnnyxbell avatar Feb 15 '21 19:02 johnnyxbell

My workaround with React Hooks is to have a useEffect to watch for changes to data (whether it's coming from app state or a prop) and assign to a local state variable, eg:

const [localData, setLocalData] = useState([])

useEffect(() => {
    setLocalData(data)
  }, [data])

return (
      <ResponsiveLine
        data={localData}
        ...

asaeed avatar Mar 11 '21 22:03 asaeed

@nigellima @johnnyxbell @luizfelipelaviola If i understood your problem correctly, this must fix it: You should check if each Object in dataset for the chart includes the "id" key:value, it should be updated each time you update the date: e.g. [{ id: "3asd25", data;[]},{id: "aszx9cz1", data;[]}.{id: "asd567sdf", data;[]}...] In this case, during the re-render of component, that will be caused be receiving new data, the redraw will be held without bug.

Probable variants for you: You can either update "id" field at backend on each request or generate the id on comparison in case of data change during receiving at front ( but having this logic on front is not the better practice )

Hope this could help you.

DmytroMatrosov avatar Mar 24 '21 04:03 DmytroMatrosov

@asaeed's solution worked great. Thanks mate

hjdr avatar Mar 30 '21 12:03 hjdr

@DmytroMatrosov 's solution is more performant. using localData as @asaeed mentioned, will cause a small flicker, you will see old and new line for a moment which has a very bad UX. also causes the whole chart to re-render.

alikazemkhanloo avatar Dec 29 '21 19:12 alikazemkhanloo

how do I randomize id but still have it display in the legend correctly? now legend is showing the random string now

pencilcheck avatar Feb 22 '22 04:02 pencilcheck

Bump. Ran into a similar issue when attempting to alter the data on button click. The chart rendered data which was not being passed to the chart (The data was updated properly). Both of @asaeed and @DmytroMatrosov solutions worked. However the first caused a flicker in a chart. The second is a good workaround, but our id's are being used in the legends, so I'd prefer not to change them.

Is this going to be worked on soon?

joseph-mccombs avatar Nov 09 '22 21:11 joseph-mccombs

Bump facing same issue mentioned by @joseph-mccombs any fixes? My legend and other features use ids

misterrPink1 avatar Dec 26 '23 05:12 misterrPink1