Repository: h2oai/wave
File: ui/src/plot.tsx (TypeScript)

Code before:
          raw_data = unpack<any[]>(model.data),
          raw_plot = unpack<Plot>(model.plot),
          marks = raw_plot.marks.map(refactorMark),
          plot: Plot = { marks },
          space = spaceTypeOf(raw_data, marks),
          data = refactorData(raw_data, plot.marks),
          { Chart } = await import('@antv/g2'),
          chart = plot.marks ? new Chart(makeChart(el, space, plot.marks, model.interactions || [])) : null
        currentPlot.current = plot
        if (chart) {
          currentChart.current = chart
          chart.data(data)
          if (model.events) {
            for (const event of model.events) {
              switch (event) {
                case 'select_marks': {
                  chart.interaction('element-single-selected')
                  chart.on('element:click', (ev: any) => {
                    const
                      { x, data: eventData } = ev,
                      { points, data, shape } = eventData,
                      selectedElements = chart.getElementsBy(el => el.getStates().includes('selected')),
                      dataInterval = shape === 'line' ? data.filter((item: any, idx: number) => {
                        if (idx !== points.length - 1 && x >= points[idx].x && x <= points[idx + 1].x) return true
                        if (idx !== 0 && x <= points[idx].x && x >= points[idx - 1].x) return true
                      }) : data
                    if (selectedElements.length && model.name) wave.emit(model.name, event, [dataInterval])
                  })
                }
              }
            }
          }
          chart.render()
          // React fires mount lifecycle hook before Safari finishes Layout phase so we need recheck if original card dimensions are the
          // same as after Layout phase. If not, rerender the plot again.
          setTimeout(() => checkDimensionsPostInit(el.clientWidth, el.clientHeight), 300)
        }
      }

    // eslint-disable-next-line react-hooks/exhaustive-deps
    React.useEffect(() => { init() }, [])
    React.useEffect(() => {
      const el = container.current
      if (!el || !currentChart.current || !currentPlot.current) return
      const
        raw_data = unpack<any[]>(model.data),
        data = refactorData(raw_data, currentPlot.current.marks)
      currentChart.current.changeData(data)

    }, [currentChart, currentPlot, model])


Review comment (suggestion):
```suggestion
                      dataInterval = shape === 'line' ? data.filter((item: any, idx: U) =>
                        (idx !== points.length - 1 && x >= points[idx].x && x <= points[idx + 1].x)
                        || (idx !== 0 && x <= points[idx].x && x >= points[idx - 1].x)
                      ) : data
```

Code after:
      currentChart = React.useRef<Chart | null>(null),
      currentPlot = React.useRef<Plot | null>(null),
      checkDimensionsPostInit = (w: F, h: F) => { // Safari fix
        const el = container.current
        if (!el) return
        if (el.clientHeight !== h || el.clientWidth !== w) {
          currentChart.current?.destroy()
          init()
        }
      },
      init = async () => {
        // Map CSS var colors to their hex values.
        cat10 = cat10.map(cssVarValue)
        const el = container.current
        if (!el) return
        // If card does not have specified height, it uses content. Since the wrapper is empty, it takes very little space - set to 300px by default.
        if (el.clientHeight < 30) el.style.height = '300px'
        const
          raw_data = unpack<any[]>(model.data),
          raw_plot = unpack<Plot>(model.plot),
          marks = raw_plot.marks.map(refactorMark),
          plot: Plot = { marks },
          space = spaceTypeOf(raw_data, marks),
          data = refactorData(raw_data, plot.marks),
          { Chart } = await import('@antv/g2'),
          chart = plot.marks ? new Chart(makeChart(el, space, plot.marks, model.interactions || [])) : null
        currentPlot.current = plot
        if (chart) {
          chart.tooltip({
            showCrosshairs: true,
            crosshairs: { type: 'xy' },
            domStyles: {
              'g2-tooltip': {
                backgroundColor: cssVar('$card'),
                color: cssVar('$text')
              },
            },
            customContent: (_title, items) => {
              ReactDOM.render(<PlotTooltip items={items} />, tooltipContainer)
              return tooltipContainer
            }
          })
          currentChart.current = chart
          chart.data(data)
          model.plot.marks.forEach(({ interactive = true }, idx) => chart.geometries[idx].customInfo({ interactive }))
          if (model.events) {
            for (const event of model.events) {
              switch (event) {
                case 'select_marks': {
                  chart.interaction('element-single-selected')
                  chart.on('element:statechange', (ev: any) => {