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.

108 lines (107 loc) 4.05 kB
import { createCanvas, Canvas, CanvasRenderingContext2D } from 'canvas'; import { NodeChartRenderer } from '../renderer/index.js'; export class ComparisonRenderer { canvas; ctx; width; height; charts = []; layout; constructor(width, height, layout = { type: 'side-by-side' }) { this.width = width; this.height = height; this.layout = layout; this.canvas = createCanvas(width, height); this.ctx = this.canvas.getContext('2d'); } addChart(chartData, position) { const defaultPosition = this.calculatePosition(this.charts.length, this.charts.length + 1); this.charts.push({ data: chartData, position: position || defaultPosition }); } async renderComparison() { this.ctx.clearRect(0, 0, this.width, this.height); this.ctx.fillStyle = '#1e222d'; this.ctx.fillRect(0, 0, this.width, this.height); for (let i = 0; i < this.charts.length; i++) { const chart = this.charts[i]; await this.renderChartInPosition(chart); } } async renderChartInPosition(chart) { const { data, position } = chart; const renderer = new NodeChartRenderer(position.width, position.height); const adjustedData = { ...data, config: { ...data.config, width: position.width, height: position.height, margin: this.adjustMargins(data.config.margin, position.width, position.height) } }; await renderer.renderChart(adjustedData); const chartCanvas = renderer.chartElement(); this.ctx.drawImage(chartCanvas, position.x, position.y); } calculatePosition(index, totalCharts) { const gap = this.layout.gap || 10; if (this.layout.type === 'side-by-side') { const chartWidth = (this.width - gap) / Math.max(this.charts.length, 2); return { x: index * (chartWidth + gap), y: 0, width: chartWidth, height: this.height }; } if (this.layout.type === 'grid') { const maxCharts = Math.min(totalCharts || this.charts.length, 2); const columns = Math.min(this.layout.columns || 2, 2); const rows = Math.ceil(maxCharts / columns); const chartWidth = (this.width - (columns - 1) * gap) / columns; const chartHeight = (this.height - (rows - 1) * gap) / rows; const row = Math.floor(index / columns); const col = index % columns; return { x: col * (chartWidth + gap), y: row * (chartHeight + gap), width: chartWidth, height: chartHeight }; } return { x: 0, y: 0, width: this.width, height: this.height }; } adjustMargins(originalMargin, width, height) { const scale = Math.min(width / 800, height / 600); const defaultMargin = { top: 60, bottom: 40, left: 60, right: 40 }; const margin = originalMargin || defaultMargin; return { top: Math.max((margin.top || 60) * scale, 20), bottom: Math.max((margin.bottom || 40) * scale, 15), left: Math.max((margin.left || 60) * scale, 20), right: Math.max((margin.right || 40) * scale, 15) }; } async exportComparison(options) { if (options.format === 'jpeg' || options.format === 'jpg') { return this.canvas.toBuffer('image/jpeg', { quality: options.quality || 0.9 }); } return this.canvas.toBuffer('image/png'); } async saveComparison(outputPath, options) { const buffer = await this.exportComparison(options); const fs = await import('fs/promises'); await fs.writeFile(outputPath, buffer); } getCanvas() { return this.canvas; } }