0

Recently I picked up a project that has d3-flame-graph on it and the graph is displayed according to the Filters defined on another component. My issue is that when searching with new parameters I can't seem to clean the previous chart and I was wondering if someone could help me. Basically what I'm having right now is, when I first enter the page, the loading component, then I have my graph and when I search for a new date I have the loading component but on top of that I still have the previous graph

I figured I could use flamegraph().destroy() on const updateGraph but nothing is happening

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 && 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 && 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
ItzaMi
  • 340
  • 6
  • 22

1 Answers1

0

Firstly, flamegraph() creates a new instance of flamegraph, you'd need to use graphref.current.destroy(). Secondly, you'd want to destroy this not when the data has already been loaded, but just as it starts to load, right? Because that's the operation that takes time.

Consider the following:

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
  )
  ...
}
Ruben Helsloot
  • 12,582
  • 6
  • 26
  • 49