Charts

Line Chart

npm install react-chartjs-2 chart.js date-fns chartjs-adapter-date-fns hex-to-rgba --save
"use client";
import React, { useEffect, useState } from "react";
import { Line } from "react-chartjs-2";
import "chartjs-adapter-date-fns";
import { Chart, registerables } from "chart.js";
import hexToRgba from "hex-to-rgba";

Chart.register(...registerables);

const setChartData = (color: string = "#2563eb", isDarkTheme = false) => {
  const startDate = new Date(2023, 0, 0);
  const chartData = Array(12)
    .fill(null)
    .map((_, i) => {
      const date = new Date(startDate);
      date.setMonth(startDate.getMonth() + i);

      return {
        x: date,
        y: Math.floor(Math.random() * 50) + 20,
      };
    });
  return {
    labels: undefined,
    datasets: [
      {
        label: "My Data",
        data: chartData,
        fill: true,
        borderColor: color,
        tension: 0.3,
        borderWidth: 1.2,
        pointBorderColor: "rgba(0, 0, 0, 0)",
        pointBackgroundColor: "rgba(0, 0, 0, 0)",
        pointHoverBackgroundColor: color,
        pointHoverBorderColor: isDarkTheme
          ? "rgba(0,0,0,1)"
          : "rgba(255,255,255,1)",
        pointBorderWidth: 8,
        hideInLegendAndTooltip: false,
        pointStyle: "circle",
        pointRadius: 4,
        backgroundColor: (context: {
          chart: { chartArea: any; ctx?: any; data?: any };
        }) => {
          if (!context.chart.chartArea) {
            return;
          }

          const {
            ctx,
            data,
            chartArea: { top, bottom },
          } = context.chart;

          const rgbaColor = hexToRgba(color, "0.2");
          const bg = [rgbaColor, "transparent"];

          const gradientBg = ctx.createLinearGradient(0, top, 0, bottom);
          const colorTranches = 1 / (bg.length - 1);

          for (let i = 0; i < bg.length; i++) {
            gradientBg.addColorStop(0 + i * colorTranches, bg[i]);
          }

          return gradientBg;
        },
      },
    ],
  };
};

const setChartOptions = ( isDarkTheme = false) => {
  return {
    interaction: {
      intersect: false,
      mode: "index",
    },
    responsive: true,
    maintainAspectRatio: false,
    layout: {
      padding: 0,
    },
    plugins: {
      tooltip: {
        enabled: false,
        position: "nearest",
        external: function (context: { chart: any; tooltip: any }) {
          const { chart, tooltip } = context;
          let tooltipEl = chart.canvas.parentNode.querySelector(
            "div.chartjs-tooltip"
          );
          if (!tooltipEl) {
            tooltipEl = document.createElement("div");
            tooltipEl.classList.add(
              "chartjs-tooltip",
              "px-2",
              "py-1",
              "dark:bg-neutral-900",
              "bg-white",
              "rounded-[8px]",
              "opacity-100",
              "flex",
              "items-center",
              "justify-center",
              "border",
              "pointer-events-none",
              "absolute",
              "-translate-x-1/2",
              "transition-all",
              "duration-[0.05s]",
              "shadow-[0px_1px_2px_0px_rgba(18,18,23,0.05)]"
            );
            chart.canvas.parentNode.appendChild(tooltipEl);
          }

          if (tooltip.opacity === 0) {
            tooltipEl.style.opacity = 0;

            return;
          }

          if (tooltip.body) {
            const bodyLines = tooltip.body.map((b: { lines: string[] }) => {
              const strArr = b.lines[0].split(":");
              const data = {
                text: strArr[0].trim(),
                value: strArr[1].trim(),
              };

              return data;
            });

            tooltipEl.innerHTML = "";
            bodyLines.forEach((body: { value: string }, i: any) => {
              const text = document.createElement("div");
              text.appendChild(document.createTextNode(body.value));
              text.classList.add(
                "text-sm",
                "text-neutral-950",
                "dark:text-neutral-0",
                "font-medium"
              );
              text.style.fontSize = "14px";
              tooltipEl.appendChild(text);
            });
          }

          const { offsetLeft: positionX, offsetTop: positionY } = chart.canvas;

          tooltipEl.style.opacity = 1;
          tooltipEl.style.left = positionX + tooltip.caretX + "px";
          tooltipEl.style.top = positionY + tooltip.caretY - 45 + "px";
        },
      },
      legend: {
        display: false,
      },
      title: {
        display: false,
      },
    },
    scales: {
      x: {
        type: "time",
        time: {
          unit: "month",
          tooltipFormat: "MM/dd/yyyy HH:mm",
        },
        offset: false,
        grid: {
          display: true,
          lineWidth: 1.2,
          color: isDarkTheme ? "#262626" : "#e5e5e5",
        },
        ticks: {
          color: isDarkTheme ? "#737373" : "#a3a3a3",
          padding: 2,
          autoSkip: false,
          maxRotation: 0,
          source: "auto",
        },
        border: {
          display: false,
          dash: [4, 2],
        },
      },
      y: {
        beginAtZero: true,
        display: false,
        min: 0,
      },
    },
  };
};

const setChartPlugins = (isDarkTheme = false) => {
  const hoverLine = {
    id: "hoverLine",
    afterDatasetsDraw: (chart: {
      ctx: any;
      tooltip: any;
      chartArea: {
        top: any;
        bottom: any;
        left: any;
        right: any;
        width: any;
        height: any;
      };
      scales: { x: any; y: any };
    }) => {
      const {
        ctx,
        tooltip,
        chartArea: { top, bottom, left, right, width, height },
        scales: { x, y },
      } = chart;

      if (tooltip?._active.length > 0) {
        const xCoor = x.getPixelForValue(tooltip.dataPoints[0].raw.x);
        const yCoor = y.getPixelForValue(tooltip.dataPoints[0].parsed.y);
        ctx.save();
        ctx.beginPath();
        ctx.lineWidth = 1.2;
        ctx.strokeStyle = tooltip.dataPoints[0].dataset.borderColor;
        ctx.setLineDash([4, 2]);
        ctx.moveTo(xCoor, yCoor);
        ctx.lineTo(xCoor, bottom + 8);
        ctx.stroke();
        ctx.closePath();
        ctx.restore();
      }
    },
  };

  return [hoverLine];
};

const color = "#2563eb";
const LineChart = () => {
  const [data, setData] = useState<any>();
  const [options, setOptions] = useState<Object>();
  const [plugins, setPlugins] = useState<any>();
 
  useEffect(() => {
    if (!color) return;
    const data = setChartData(color);
    const options = setChartOptions();
    const plugins = setChartPlugins();

    setData(data);
    setOptions(options);
    setPlugins(plugins);
  }, []);

  if(!data) return;
  return (
    {data && (
        <div className="w-full lg:w-96 overflow-auto mx-auto">
            <div className="min-w-[480px] lg:min-w-0 max-h-36 mx-auto">
            <Line
                className="w-full h-full"
                data={data}
                options={options}
                plugins={plugins}
            />
            </div>
        </div>
    )}
  );
};

export default LineChart;

Multi-Line Chart

const setChartData = ( 
  lineCount: number = 2, 
  colors: string[] = ["#2563eb", "#9333ea", "#e11d48", "#f97316"],  
  isDarkTheme = false 
) => { 
  const startDate = new Date(2023, 0, 1);
  const datasets: { x: Date; y: number }[][] = Array(lineCount) 
    .fill(null) 
    .map(() => []); 
... 

Here’s a small trick to create multi-line charts, I'll share all the code with you soon. I’ll also be sharing other types of charts. Follow me on 𝕏 to stay updated → tanerengiin