@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
JavaScript
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;
}
}