Home > Enterprise >  React loading data for d3 linechart
React loading data for d3 linechart

Time:06-18

I'm both new to d3 and React, and I'm trying to draw a linechart with d3 with data I get from my backend through a async rest call. When I used static data from a .csv file, it worked. But when I try to use the backend data, neither the xScale is on the correct position nor the lines are shown.

As far I can see from my console outputs, the data is formatted and fetched correctly when I'm trying to draw the lines. Since neither the xScale or my lines are visible, my guess is that the zoom also doesn't work anymore.

Edit: I also noticed that the xAxis is displayed on top even when I use d3.axisBottom

Hope someone can help me.


type ChartDataType = {
  timestamp: Date,
  value: number
}

const LineChart = ({
                     width, height, top, right, bottom, left, fill, url, equipmentUUID
                   }: ZoomBasicLineChartProps) => {
  const dispatch = useDispatch<AppDispatch>()
  const [data, setData] = useState<ChartDataType[]>()
  const title = useRef<string>('Default title, use label')
  const containerRef = useRef<HTMLDivElement>()
  let container: Selection<SVGGElement, unknown, HTMLElement, unknown>;
  let zoom: d3.ZoomBehavior<HTMLDivElement, unknown>;

  const chartWidth = width - left - right
  const chartHeight = height - top - bottom

  let xAxis: Selection<SVGGElement, unknown, HTMLElement, unknown>;
  let yAxis: Selection<SVGGElement, unknown, HTMLElement, unknown>;
  let path: any;

  const diagramDTO = useSelector(LineChartDiagramSelectors.strippedLineChartDiagramData)

  const loadStrippedLineChartDiagramData = useCallback(() => {
    dispatch(LineChartDiagramDataThunks.getLineChartDiagramDataArray("services/performancemanagement/api/diagram/line-chart/7f5a2e69-0a51-4131-a77f-601ae9de24c6/0/SchlagstatistikGes_VR1?since=148"))
  }, [dispatch])


  const containerSelection = useMemo(() => (
    d3.select<HTMLDivElement, unknown>('#ZoomLineChart')
  ), [containerRef.current])


  const xScale = useMemo(() => {
    const domain = data ? d3.extent(data, (d) => d.timestamp) : [new Date(), new Date()]
    console.log("domain")
    console.log(domain)
    return d3
      .scaleTime()
      .domain([domain[0] ?? new Date(), domain[1] ?? new Date()])
      .range([0, width])
  }, [data])

  const yScale = useMemo(() => {
    const domain = data ? d3.extent(data, (d) => d.value) : [0, 0]
    return d3
      .scaleLinear()
      .domain([domain[0] ?? 0, domain[1] ?? 0])
      .range([height, 0])
  }, [data])

  const initSvg = (): SelectionType => (
    containerSelection
      .append('svg')
      .attr('width', chartWidth   left   right)
      .attr('height', chartHeight   top   bottom   75)
      .append('g')
      .attr('transform', `translate(${left},${top})`)
  )


  const drawAxes = (g: SelectionType): void => {
    xAxis = g.append('g')
      .call(d3.axisBottom(xScale))
    yAxis = g.append('g')
      .call(d3.axisLeft(yScale))
  }

  const drawLabel = (g: SelectionType): void => {
    g.append('text')
      .attr('text-anchor', 'start')
      .attr('y', height   40)
      .attr('x', 0)
      .text(title.current)
  }


  const drawLines = (g: SelectionType): void => {
    if (data) {
      const lines = d3.line<ChartDataType>()
        .x(d => {
          // console.log(d.timestamp)
          return xScale(d.timestamp)
        })
        .y(d => {
          // console.log(d.value)
          return yScale(d.value)
        })
      console.log("data")
      console.log(data)

      path = g
        .append('g')
        .attr('clip-path', "url(#clip)")
        .append('path')
        .datum(data)
        .attr('class', 'line')
        .attr('fill', 'none')
        .attr('stroke', fill)
        .attr('stroke-width', 1.5)
        .attr('d', lines)
    }
  }

  function updateChart(event: any) {
    const {transform} = event;
    const newX = transform.rescaleX(xScale);
    xAxis.call(d3.axisBottom(newX));

    path.attr("d", d3.line<ChartDataType>()
      .x(d => newX(d.timestamp))
      .y(d => yScale(d.value)));
  }

  const clip = (g: SelectionType): void => {
    g.append("defs")
      .append("SVG:clipPath")
      .attr("id", "clip")
      .append("SVG:rect")
      .attr("width", width)
      .attr("height", height)
      .attr("x", 0)
      .attr("y", 0);
  }

  const initZoom = (g: SelectionType): void => {
    zoom = d3.zoom<HTMLDivElement, unknown>()
      .scaleExtent([0.5, 5])
      .on('zoom', updateChart)
    containerSelection.call(zoom)
  }

  // Parse into data object
  useEffect(() => {
    if (diagramDTO) {
      const updatedData = diagramDTO.payload?.map(dataPoint => {
        const value =  dataPoint.value
        return {timestamp: dataPoint.timestamp, value}
      })
      setData(updatedData)
    } else {
      loadStrippedLineChartDiagramData()
    }
  }, [diagramDTO])


  useEffect(() => {
    container = initSvg();
    initZoom(container)
    clip(container)
    drawAxes(container)
    drawLines(container)
    drawLabel(container)
  }, [data])

  return (
    <>
      <Box id='ZoomLineChart' ref={containerRef}/>
    </>
  )
}

Example data playload:

{
  "unit" : "unit.schlagstatistikges_vr1",
  "label" : "label.schlagstatistikges_vr1",
  "payload" : [ {
    "timestamp" : "2022-06-08T03:22:00Z",
    "value" : "10676"
  }, {
    "timestamp" : "2022-06-08T03:23:00Z",
    "value" : "10583"
  }, {
    "timestamp" : "2022-06-08T03:24:00Z",
    "value" : "10647"
  }, {
    "timestamp" : "2022-06-08T03:25:00Z",
    "value" : "10585"
  }, {
    "timestamp" : "2022-06-08T03:26:00Z",
    "value" : "10644"
  }, {
    "timestamp" : "2022-06-08T03:27:00Z",
    "value" : "10227"
  }, {
    "timestamp" : "2022-06-08T03:28:00Z",
    "value" : "10620"
  }, {
    "timestamp" : "2022-06-08T03:29:00Z",
    "value" : "10635"
  }, {
    "timestamp" : "2022-06-08T03:30:00Z",
    "value" : "10432"
  }, {
    "timestamp" : "2022-06-08T03:31:00Z",
    "value" : "10295"
  }, {
    "timestamp" : "2022-06-08T03:32:00Z",
    "value" : "10674"
  }, {
    "timestamp" : "2022-06-08T03:33:00Z",
    "value" : "10715"
  }, {
    "timestamp" : "2022-06-08T03:34:00Z",
    "value" : "10068"
  }, {
    "timestamp" : "2022-06-08T03:35:00Z",
    "value" : "10262"
  }, {
    "timestamp" : "2022-06-08T03:36:00Z",
    "value" : "10926"
  }, {
    "timestamp" : "2022-06-08T03:37:00Z",
    "value" : "10271"
  }, {
    "timestamp" : "2022-06-08T03:38:00Z",
    "value" : "10870"
  } ],
  "color" : "#80BEBF"
}

CodePudding user response:

I found the problem: my IDE recognized the date object, but d3 required an explicit timeparse:

const updatedData = diagramDTO.payload?.map(dataPoint => {
        const parser = d3.timeParse('%Y-%m-%dT%H:%M:%S.%LZ')
        const timestamp = parser(String(dataPoint.timestamp))!
        const value =  dataPoint.value
        return {timestamp, value}
      })
  • Related