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

Code before:
            // XXX pass container element
            customContent: (title, items) => {
              const container = document.createElement('div')
              container.className = 'g2-tooltip'
              container.innerHTML = items.map(({ data, mappingData, color }) =>
                Object.keys(data).map(item =>
                  `<li class="g2-tooltip-list-item" data-index={index} style="margin-bottom:4px;display:flex;align-items: center;">
                    <span style="background-color:${mappingData?.color || color};" class="g2-tooltip-marker"></span>
                    <span style="display:inline-flex;flex:1;justify-content:space-between">
                    <span style="margin-right: 16px;">${item}:</span><span>${data[item] instanceof Date ? data[item].toISOString().split('T')[0] : data[item]}</span>
                    </span>
                  </li>`
                ).join('')
              ).join('')
              return container
            }
          })
          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:statechange', (ev: any) => {
                    const e = ev.gEvent.originalEvent
                    if (e.stateStatus && e.state === 'selected') {
                      if (model.name) wave.emit(model.name, event, [e.element?.data])
                    }
                  })
                }
              }
            }
          }
          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)


Review comment (suggestion):
Let's use React here for easier further maintenance. 
```suggestion
              return ReactDOM.render(<YourTooltipReactComponent />, container)
```

Code after:
      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)
          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) => {
                    const e = ev.gEvent.originalEvent
                    if (e.stateStatus && e.state === 'selected') {
                      if (model.name) wave.emit(model.name, event, [e.element?.data])
                    }
                  })
                }
              }
            }
          }
          chart.render()
          // React fires mount lifecycle hook before Safari finishes Layout phase so we need recheck if original card dimensions are the