Line Chart with Gradient
Code
ChartUtil.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"use client";
// reference: https://d3-graph-gallery.com/graph/line_color_gradient_svg.html
import React, { useEffect, useCallback, useRef } from "react";
import * as d3 from "d3";
import { useResizeObserver } from "@/hooks/useResizeObserver";
import { ChartProps } from "@/types";
const ChartUtil = ({ data: rawData }: ChartProps) => {
  const data = Object.values(rawData).filter((n: any) => n.value !== undefined);
  const containerRef = useRef<HTMLDivElement | null>(null);
  const renderFunc = useCallback(async () => {
    const container = containerRef.current as HTMLElement;
    if (!container) return;
    const margin = { top: 10, right: 10, bottom: 30, left: 70 },
      width = container.offsetWidth - margin.left - margin.right,
      height = 600 - margin.top - margin.bottom;
    const svg = d3
      .select(container)
      .html("")
      .append("svg")
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.top + margin.bottom)
      .append("g")
      .attr("transform", `translate(${margin.left},${margin.top})`);
    const viewData = data.map((d: any) => ({
      date: d3.timeParse("%Y-%m-%d")(d.date),
      value: d.value,
    }));
    const x = d3
      .scaleTime()
      .domain(
        d3.extent(viewData, function (d) {
          return d.date;
        }) as any
      )
      .range([0, width]);
    svg
      .append("g")
      .attr("transform", `translate(0, ${height})`)
      .call(d3.axisBottom(x))
      .call((d) =>
        d
          .selectAll("text")
          .attr("font-family", "Space Mono")
          .attr("font-size", "14px")
      );
    // Max value observed:
    const max = d3.max(data, function (d: any) {
      return +d.value;
    });
    // Add Y axis
    const y = d3
      .scaleLinear()
      .domain([0, max ?? 0])
      .range([height, 0]);
    svg
      .append("g")
      .call(d3.axisLeft(y))
      .call((d) =>
        d
          .selectAll("text")
          .attr("font-family", "Space Mono")
          .attr("font-size", "14px")
      );
    svg
      .append("linearGradient")
      .attr("id", "line-gradient")
      .attr("gradientUnits", "userSpaceOnUse")
      .attr("x1", 0)
      .attr("y1", y(0))
      .attr("x2", 0)
      .attr("y2", y(max ?? 0))
      .selectAll("stop")
      .data([
        { offset: "0%", color: "#038aff" },
        { offset: "100%", color: "#ff037d" },
      ])
      .enter()
      .append("stop")
      .attr("offset", function (d) {
        return d.offset;
      })
      .attr("stop-color", function (d) {
        return d.color;
      });
    svg
      .append("path")
      .datum(viewData)
      .attr("fill", "none")
      .attr("stroke", "url(#line-gradient)")
      .attr("stroke-width", 2)
      .attr(
        "d",
        d3
          .line()
          .x(function (d: any) {
            return x(d.date);
          })
          .y(function (d: any) {
            return y(d.value);
          }) as any
      );
  }, [data]);
  useEffect(() => {
    containerRef.current !== null && renderFunc();
  }, [renderFunc]);
  const resizeHandler = useCallback(() => {
    containerRef.current !== null && renderFunc();
  }, [renderFunc]);
  useResizeObserver(containerRef, resizeHandler);
  return <div ref={containerRef}></div>;
};
export default ChartUtil;