DEV Community

simplyCode
simplyCode

Posted on • Edited on

Building a Real-Time Candlestick Chart with Next.js, Lightweight Charts, and InsightSentry

In this tutorial, we'll create a dynamic, real-time candlestick chart that visualizes cryptocurrency price data. We'll leverage Next.js for our frontend framework, Lightweight Charts for fast and interactive charting, and the InsightSentry API for streaming live market data via WebSockets.

Prerequisites:

  • Basic understanding of React and Next.js.
  • Node.js and npm (or yarn) installed on your system.
  • An API key from InsightSentry (https://insightsentry.com/).

Project Setup:

  1. Create a Next.js App:

    npx create-next-app@latest my-crypto-chart
    cd my-crypto-chart
    
  2. Install Dependencies:

    npm install lightweight-charts
    

Code Structure:

We'll create a single component for our chart within the app directory (or pages directory if you are not using the app router). Let's name it Chart.tsx (or Chart.jsx if you prefer JavaScript).

Chart Component (Chart.tsx):

"use client";

import { createChart, ChartOptions, DeepPartial, UTCTimestamp, CandlestickSeries } from "lightweight-charts";
import { useCallback, useEffect, useRef } from "react";

// Replace with your actual InsightSentry API key
const wsapikey = "your key";

// Helper functions for time formatting
const formatTimeToHHMMSS = (timestamp: UTCTimestamp) => {
  const date = new Date(timestamp * 1000); // Convert to milliseconds
  return date.toLocaleTimeString('en-US', { hour12: false }); // Convert to 24-hour hh:mm:ss format
};
const formatDateToLocalTime = (timestamp: UTCTimestamp) => {
  const date = new Date(timestamp * 1000); // Convert to milliseconds
  return date.toLocaleString(); // Convert to local time string
};

// Chart options (customization)
const chartOptions: DeepPartial<ChartOptions> = {
  layout: {
    textColor: '#1e293b',
    background: { color: 'white' },
    attributionLogo: false
  },
  grid: {
    vertLines: {
      visible: false,
    },
    horzLines: {
      visible: false,
    },
  },
  autoSize: true,


  timeScale: {
    visible: true,
    timeVisible: true,
    ticksVisible: true,

    tickMarkFormatter: formatTimeToHHMMSS,
    borderColor: "#D1D5DB",
  },

  localization: {
    timeFormatter: formatDateToLocalTime,

  },
  rightPriceScale: {
    borderColor: "#e2e8f0",
  },

  crosshair: {
    horzLine: {
      visible: true,
      labelVisible: true,
      color: 'rgba(59, 130, 246, 0.5)', // Semi-transparent blue
    },
    vertLine: {
      visible: true,
      labelVisible: true,
      color: 'rgba(59, 130, 246, 0.5)', // Semi-transparent blue
    },
  },

};

export default function Chart() {
  const chartContainerRef = useRef<HTMLDivElement>(null);
  const wsRef = useRef<WebSocket | null>(null);
  const chartRef = useRef<any>(null);
  const seriesRef = useRef<any>(null);

  // WebSocket connection function (memoized with useCallback)
  const connectWebSocket = useCallback(() => {
    if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
      console.log("WebSocket already connected.");
      return;
    }
    const ws = new WebSocket('wss://realtime.insightsentry.com/live');
    ws.onopen = () => {
      const subMsg = JSON.stringify({
        api_key: wsapikey,
        subscriptions: [
          { code: 'BINANCE:BTCUSDT', bar_type: 'second', bar_interval: 1, type: 'series' }
        ]
      });
      ws.send(subMsg);
    };
    ws.onerror = () => {
      console.error('WebSocket error');
    }
    ws.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data)
        if (!data.code) {
          return
        }

        if (data.series && seriesRef.current) {
          const formattedSeries = data.series.map(
            (item: {
              time: number
              open: number
              high: number
              low: number
              close: number
              volume: number
            }) => ({
              time: item.time,
              open: item.open,
              high: item.high,
              low: item.low,
              close: item.close,
              volume: item.volume
            })
          )
          for (const item of formattedSeries) {
            seriesRef.current.update(item)
          }
        }
      } catch (error) {
        console.error(error)
      }
    }

    ws.onclose = () => {
      console.log('WebSocket closed');
      setTimeout(() => {
        connectWebSocket();
      }, 2000);
    };

    wsRef.current = ws;
  }, []);

  // useEffect for chart creation and WebSocket connection
  useEffect(() => {
    if (chartContainerRef.current) {
      chartRef.current = createChart(chartContainerRef.current, chartOptions);
      seriesRef.current = chartRef.current.addSeries(CandlestickSeries, {
        priceFormat: {
          type: "price",
          minMove: 0.001
        },
        upColor: "#10B981",
        downColor: "#EF4444",
        borderVisible: false,
        wickUpColor: "#10B981",
        wickDownColor: "#EF4444",
      });
      seriesRef.current.setData([]);
      chartRef.current.timeScale().fitContent();

      connectWebSocket();
    }

    return () => {
      if (wsRef.current) {
        wsRef.current.close();
        wsRef.current = null;
      }
      if (chartRef.current) {
        chartRef.current.remove();
      }
    };
  }, [connectWebSocket]);

  return (
    <div ref={chartContainerRef} className="w-full h-[500px]" />
  );
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  1. Imports: Import necessary modules from lightweight-charts and react.
  2. API Key: Replace YOUR_INSIGHTSENTRY_API_KEY with your actual API key.
  3. Helper Functions: formatTimeToHHMMSS and formatDateToLocalTime format the timestamps for the chart's time scale.
  4. Chart Options: The chartOptions object configures the appearance and behavior of the chart (colors, gridlines, watermark, crosshair, etc.).
  5. Refs:
    • chartContainerRef: A ref to the DOM element where the chart will be rendered.
    • wsRef: A ref to store the WebSocket connection.
    • chartRef: A ref to the created chart object.
    • seriesRef: A ref to the candlestick series object.
  6. connectWebSocket Function:
    • Establishes a WebSocket connection to the InsightSentry API.
    • Sends a subscription message to receive real-time data for BINANCE:BTCUSDT (you can change this to other symbols).
    • Handles incoming data (onmessage) and updates the chart's series with the new data points.
    • Implements reconnection logic (onclose) to automatically reconnect if the connection drops.
  7. useEffect Hook:
    • Creates the chart using createChart and attaches it to the chartContainerRef element.
    • Adds a candlestick series to the chart using addCandlestickSeries.
    • Sets initial data to an empty array ([]).
    • Calls connectWebSocket to initiate the WebSocket connection.
    • Cleanup Function: Closes the WebSocket connection and removes the chart when the component unmounts.
  8. JSX: Renders a div element with the chartContainerRef to serve as the container for the chart.

Integrating the Chart Component:

Now, import and use the Chart component in your main page component (e.g., app/page.tsx):

import Chart from './Chart'; 

export default function Home() {
  return (
    <main>
      <h1>Real-Time Crypto Chart</h1>
      <Chart />
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

Running the Application:

Start the Next.js development server:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Open your browser and go to http://localhost:3000 (or the port indicated in your console). You should see a beautiful candlestick chart updating in real-time with data from InsightSentry!

Output

Customization:

  • Chart Options: Explore more options provided by Lightweight Charts to customize the chart's appearance (colors, grid, axes, etc.). Refer to the Lightweight Charts documentation for details.
  • Data Subscription: Modify the subscriptions array in the connectWebSocket function to subscribe to different symbols or data types offered by the InsightSentry API.
  • Error Handling: Add more robust error handling to the onmessage event handler to gracefully handle potential issues with the data received from the API.
  • UI Enhancements: Add UI elements like a symbol selector, time interval controls, or technical indicators to enhance the user experience.

Conclusion:

This tutorial demonstrated how to build a real-time candlestick chart using Next.js, Lightweight Charts, and the InsightSentry API. You can expand upon this foundation to create sophisticated financial dashboards and data visualization tools. Remember to consult the documentation for Lightweight Charts and InsightSentry to explore their full capabilities. Happy charting!

Top comments (0)