Удаление d3-flame-graph при загрузке новых данных

#reactjs #d3.js #flamegraph

#reactjs #d3.js #flamegraph

Вопрос:

Недавно я взял проект, в котором есть d3-flame-graph , и график отображается в соответствии с фильтрами, определенными в другом компоненте. Моя проблема в том, что при поиске с новыми параметрами я, похоже, не могу очистить предыдущую диаграмму, и мне было интересно, может ли кто-нибудь мне помочь. По сути, что у меня происходит прямо сейчас, когда я впервые захожу на страницу, загружается компонент, затем у меня есть мой график, и когда я ищу новую дату, у меня есть компонент загрузки, но поверх этого у меня все еще есть предыдущий график

Я подумал, что мог бы использовать flamegraph().destroy() on const updateGraph , но ничего не происходит

 import React, { FC, useEffect, useRef, useState, useCallback } from 'react'
import { useParams } from 'react-router-dom'
import moment from 'moment'
import * as d3 from 'd3'
import { flamegraph } from 'd3-flame-graph'

import Filters, { Filter } from '../../../../../../components/Filters'
import { getFlamegraph } from '../../../../../../services/flamegraph'
import { useQueryFilter } from '../../../../../../hooks/filters'
import FlamegraphPlaceholder from '../../../../../../components/Placeholders/Flamegraph'

import css from './flamegraph.module.css'

import ToastContainer, {
  useToastContainerMessage,
} from '../../../../../../components/ToastContainer'

const defaultFilters = {
  startDate: moment().subtract(1, 'month'),
  endDate: moment(),
  text: '',
  limit: 10,
}

const getOffSet = (divElement: HTMLDivElement | null) => {
  if (divElement !== null) {
    const padding = 100
    const minGraphHeight = 450

    // ensure that the graph has a min height
    return Math.max(
      window.innerHeight - divElement.offsetTop - padding,
      minGraphHeight
    )
  } else {
    const fallBackNavigationHeight = 300

    return window.innerHeight - fallBackNavigationHeight
  }
}

const Flamegraph: FC = () => {
  const [queryFilters, setQueryFilters] = useQueryFilter(defaultFilters)
  const [fetching, setFetching] = useState(false)
  const [graphData, setGraphData] = useState()
  const {
    messages: toastMessages,
    addMessage: addMessageToContainer,
    removeMessage: removeMessageFromContainer,
  } = useToastContainerMessage()
  const flameContainerRef = useRef<HTMLDivElement | null>(null)
  const flameRef = useRef<HTMLDivElement | null>(null)
  const graphRef = useRef<any>()
  const graphDataRef = useRef<any>()
  const timerRef = useRef<any>()

  const { projectId, functionId } = useParams()
  let [sourceId, sourceLine] = ['', '']

  if (functionId) {
    ;[sourceId, sourceLine] = functionId.split(':')
  }

  const createGraph = () => {
    if (flameContainerRef.current amp;amp; flameRef.current) {
      graphRef.current = flamegraph()
        .width(flameContainerRef.current.offsetWidth)
        .height(getOffSet(flameRef.current))
        .cellHeight(30)
        .tooltip(false)
        .setColorMapper(function(d, originalColor) {
          // Scale green component proportionally to box width (=> the wider the redder)
          let greenHex = (192 - Math.round((d.x1 - d.x0) * 128)).toString(16)
          return '#FF'   ('0'   greenHex).slice(-2)   '00'
        })
    }
  }

  const updateGraph = (newData: any) => {
    setGraphData(newData)
    graphDataRef.current = newData

    if (graphRef.current) {
      if (newData === null) {
        graphRef.current.destroy()
        graphRef.current = null
      } else {
        d3.select(flameRef.current)
          .datum(newData)
          .call(graphRef.current)
      }
    }
  }

  const fetchGraph = (filters: Filter) => {
    setFetching(true)
    getFlamegraph(
      Number(projectId),
      filters.startDate ? filters.startDate.unix() : 0,
      filters.endDate ? filters.endDate.unix() : 0,
      sourceId,
      sourceLine
    )
      .then(graphData => {
        if (!graphRef.current) {
          createGraph()
        }
        updateGraph(graphData)
      })
      .catch(({ response }) => {
        updateGraph(null)
        if (response.data) {
          addMessageToContainer(response.data.message, true)
        }
      })
      .finally(() => {
        setFetching(false)
      })
  }

  const onResize = useCallback(() => {
    clearTimeout(timerRef.current)
    timerRef.current = setTimeout(() => {
      if (graphRef.current amp;amp; flameContainerRef.current) {
        graphRef.current.width(flameContainerRef.current.offsetWidth)
        d3.select(flameRef.current)
          .datum(graphDataRef.current)
          .call(graphRef.current)
      }
    }, 500)
  }, [])

  useEffect(() => {
    fetchGraph(queryFilters)
    window.addEventListener('resize', onResize)

    return () => {
      window.removeEventListener('resize', onResize)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  const onChangeFilters = (filters: Filter) => {
    setQueryFilters(filters)
    fetchGraph(filters)
  }

  return (
    <div className={css.host}>
      <Filters
        defaultValues={queryFilters}
        searching={fetching}
        onSearch={onChangeFilters}
      />
      <div className={css.flameBox}>
        <div className={css.flameContainer} ref={flameContainerRef}>
          <div ref={flameRef} />
        </div>
        {fetching || !graphData ? (
          <FlamegraphPlaceholder loading={fetching} />
        ) : null}
      </div>
      <ToastContainer
        messages={toastMessages}
        toastDismissed={removeMessageFromContainer}
      />
    </div>
  )
}

export default Flamegraph
  

Ответ №1:

Во-первых, flamegraph() создается новый экземпляр flamegraph , который вам нужно будет использовать graphref.current.destroy() . Во-вторых, вы бы хотели уничтожить это не тогда, когда данные уже загружены, а сразу после того, как они начнут загружаться, верно? Потому что это операция, которая требует времени.

Рассмотрим следующее:

 const cleanGraph = () => {
  if (graphref.current !== undefined) {
    graphref.current.destroy()
  }
}

const fetchGraph = (filters: Filter) => {
  setFetching(true)
  cleanGraph()
  getFlamegraph(
    Number(projectId),
    filters.startDate ? filters.startDate.unix() : 0,
    filters.endDate ? filters.endDate.unix() : 0,
    sourceId,
    sourceLine
  )
  ...
}