candlestick-chart-generator
Version:
A Node.js library for generating candlestick chart screenshots using financial data
189 lines (162 loc) • 6.05 kB
JavaScript
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;