UNPKG

@neabyte/chart-to-image

Version:

Convert trading charts to images using Node.js canvas with advanced features: 6 chart types, VWAP/EMA/SMA indicators, custom colors, themes, hide elements, scaling, and PNG/JPEG export formats.

114 lines (113 loc) 4.8 kB
import { ChartConfig } from '../core/config.js'; import { NodeChartRenderer } from '../renderer/index.js'; import { DataProvider } from '../utils/provider.js'; export class ChartRenderer { config; dataProvider; nodeRenderer; constructor(config) { this.config = config; this.dataProvider = new DataProvider({ name: (config.exchange || 'binance'), sandbox: false }); this.nodeRenderer = new NodeChartRenderer(config.width, config.height); } async generateChart() { try { const data = await this.dataProvider.fetchOHLCV(this.config.symbol, this.config.timeframe, 100); const chartData = this.createChartData(data); await this.nodeRenderer.renderChart(chartData); const buffer = await this.nodeRenderer.exportChart({ format: this.getFileExtension(this.config.outputPath), quality: 0.9 }); await this.nodeRenderer.saveChart(this.config.outputPath, { format: this.getFileExtension(this.config.outputPath), quality: 0.9 }); return { success: true, outputPath: this.config.outputPath, dataUrl: `data:image/png;base64,${buffer.toString('base64')}` }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } createChartData(data) { const options = this.getChartOptions(); const ohlc = data.map(item => ({ time: item.timestamp, open: item.open, high: item.high, low: item.low, close: item.close, ...(item.volume !== undefined && { volume: item.volume }) })); const levels = options.horizontalLevels?.map(level => ({ value: level.value, color: level.color, lineStyle: level.lineStyle, ...(level.label && { label: level.label }) })) || []; return { ohlc, levels, config: options }; } getChartOptions() { const options = { width: this.config.width, height: this.config.height, backgroundColor: this.config.backgroundColor || (this.config.theme === 'dark' ? '#1e222d' : '#ffffff'), textColor: this.config.textColor || (this.config.theme === 'dark' ? '#ffffff' : '#000000'), gridColor: this.config.theme === 'dark' ? '#2b2b43' : '#e1e3e6', borderColor: this.config.theme === 'dark' ? '#2b2b43' : '#e1e3e6', chartType: this.config.chartType || 'candlestick', watermarkColor: this.config.theme === 'dark' ? '#ffffff' : '#000000', watermarkOpacity: 0.3, customBarColors: { bullish: this.config.customBarColors?.bullish || '#26a69a', bearish: this.config.customBarColors?.bearish || '#ef5350', wick: this.config.customBarColors?.wick || '#424242', border: this.config.customBarColors?.border || '#E0E0E0' }, horizontalLevels: this.config.horizontalLevels || [], title: this.config.title || `${this.config.symbol} ${this.config.timeframe}`, showTitle: this.config.showTitle !== false, showTimeAxis: this.config.showTimeAxis !== false, showGrid: this.config.showGrid !== false, showVWAP: this.config.showVWAP === true, showEMA: this.config.showEMA === true, ...(this.config.emaPeriod !== undefined && { emaPeriod: this.config.emaPeriod }), showSMA: this.config.showSMA === true, ...(this.config.smaPeriod !== undefined && { smaPeriod: this.config.smaPeriod }), showBollingerBands: this.config.showBollingerBands === true, ...(this.config.bbPeriod !== undefined && { bbPeriod: this.config.bbPeriod }), ...(this.config.bbStandardDeviations !== undefined && { bbStandardDeviations: this.config.bbStandardDeviations }), ...(this.config.bbColors !== undefined && { bbColors: this.config.bbColors }) }; if (this.config.watermark !== undefined) { options.watermark = this.config.watermark; } return options; } getFileExtension(path) { return path.split('.').pop()?.toLowerCase() || 'png'; } getChart() { return this.nodeRenderer; } async updateChart(data) { const chartData = this.createChartData(data); await this.nodeRenderer.renderChart(chartData); } destroy() { } }