@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
JavaScript
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() {
}
}