UNPKG

candlestick-chart-generator

Version:

A Node.js library for generating candlestick chart screenshots using financial data

189 lines (162 loc) 6.05 kB
const { chromium } = require('playwright'); const fs = require('fs'); const path = require('path'); class ChartRenderer { constructor(headless = true) { this.browser = null; this.page = null; this.headless = headless; } /** * Initialize the Playwright browser instance */ async initializeBrowser() { if (!this.browser) { this.browser = await chromium.launch({ headless: this.headless }); this.page = await this.browser.newPage(); } } /** * Render a candlestick chart with the provided data and options * @param {Array} data - Chart data in Lightweight Charts format * @param {Object} options - Chart rendering options * @param {number} options.width - Chart width in pixels * @param {number} options.height - Chart height in pixels * @param {Object} options.chartOptions - Lightweight Charts options */ async renderChart(data, options = {}) { if (!this.browser || !this.page) { await this.initializeBrowser(); } const { width = 1200, height = 600, chartOptions = {} } = options; // Set viewport size await this.page.setViewportSize({ width, height }); // Create HTML content with embedded chart const htmlContent = this._generateChartHTML(data, width, height, chartOptions); await this.page.setContent(htmlContent); // Wait for the page to load await this.page.waitForLoadState('networkidle'); // Wait for the chart to be ready with increased timeout try { await this.page.waitForFunction(() => window.chartReady === true, { timeout: 15000 }); } catch (error) { // If waiting for chartReady fails, just wait a fixed time console.warn('Chart ready signal not received, using fixed timeout'); await this.page.waitForTimeout(3000); } // Additional wait to ensure rendering is complete await this.page.waitForTimeout(1000); } /** * Generate HTML content with embedded chart data */ _generateChartHTML(data, width, height, chartOptions) { return ` <!DOCTYPE html> <html> <head> <title>Candlestick Chart</title> <script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.umd.js"></script> <style> body { margin: 0; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } #chart-container { width: ${width}px; height: ${height}px; } </style> </head> <body> <div id="chart-container"></div> <script> const chartData = ${JSON.stringify(data)}; const customChartOptions = ${JSON.stringify(chartOptions)}; // Function to initialize and render the chart function initializeChart() { const chartContainer = document.getElementById('chart-container'); // Default chart options const defaultOptions = { width: ${width}, height: ${height}, layout: { background: { type: LightweightCharts.ColorType.Solid, color: '#131722' }, textColor: '#d1d4dc', }, grid: { vertLines: { color: 'rgba(42, 46, 57, 0.5)' }, horzLines: { color: 'rgba(42, 46, 57, 0.5)' }, }, timeScale: { borderColor: 'rgba(197, 203, 206, 0.8)' }, rightPriceScale: { borderColor: 'rgba(197, 203, 206, 0.8)' } }; // Merge default options with custom options const finalOptions = Object.assign({}, defaultOptions, customChartOptions); // Create the chart const chart = LightweightCharts.createChart(chartContainer, finalOptions); // Add candlestick series const candlestickSeries = chart.addCandlestickSeries({ upColor: '#26a69a', downColor: '#ef5350', borderVisible: false, wickUpColor: '#26a69a', wickDownColor: '#ef5350', }); // Set the data candlestickSeries.setData(chartData); // Fit content to show all data chart.timeScale().fitContent(); // Signal that the chart is ready window.chartReady = true; } // Initialize chart when the page loads if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeChart); } else { initializeChart(); } </script> </body> </html>`; } /** * Take a screenshot of the rendered chart * @param {string} outputPath - Path where the screenshot will be saved * @returns {Promise<string>} Path to the saved screenshot */ async takeScreenshot(outputPath) { if (!this.page) { throw new Error('Chart must be rendered before taking a screenshot'); } await this.page.screenshot({ path: outputPath, fullPage: true }); return outputPath; } /** * Close the browser instance */ async closeBrowser() { if (this.browser) { await this.browser.close(); this.browser = null; this.page = null; } } /** * Get chart as base64 image data instead of saving to file * @returns {Promise<string>} Base64 encoded image data */ async getChartAsBase64() { if (!this.page) { throw new Error('Chart must be rendered before getting image data'); } const screenshot = await this.page.screenshot({ fullPage: true }); return screenshot.toString('base64'); } } module.exports = ChartRenderer;