阿里云主机折上折
  • 微信号
Current Site:Index > Server-side rendering solution

Server-side rendering solution

Author:Chuan Chen 阅读数:41114人阅读 分类: ECharts

Server-Side Rendering Solution for ECharts

The server-side rendering (SSR) solution for ECharts primarily addresses the need to generate static content for dynamic charts on the server. Traditional browser-side rendering relies on DOM operations, whereas server environments lack DOM interfaces, requiring specific methods to achieve chart rendering. Below is the complete implementation path and technical details.

Core Implementation Principle

ECharts' server-side rendering relies on libraries like node-canvas or jsdom to simulate a browser environment. The underlying process involves the following steps:

  1. Environment Simulation: Create a virtual Canvas or SVG rendering environment in Node.js.
  2. Chart Initialization: Initialize the chart instance using the same configuration options as in the browser.
  3. Data Binding: Inject data into the chart instance.
  4. Rendering Output: Generate Base64 images or SVG strings.
const { createCanvas } = require('canvas');
const echarts = require('echarts');

// Create a virtual Canvas
const canvas = createCanvas(800, 600);
const chart = echarts.init(canvas);

// Set chart configuration
chart.setOption({
  title: { text: 'Server-Side Rendering Example' },
  series: [{ type: 'bar', data: [12, 19, 3, 5, 2] }]
});

// Output PNG image
const buffer = canvas.toBuffer('image/png');
fs.writeFileSync('output.png', buffer);

Specific Implementation Solutions

Solution 1: Canvas Rendering

Use the node-canvas library to simulate a browser Canvas environment, suitable for generating static images:

const { createCanvas } = require('canvas');
const echarts = require('echarts');

function renderChart(options) {
  const canvas = createCanvas(options.width || 800, options.height || 600);
  const chart = echarts.init(canvas);
  
  chart.setOption(options.config);
  return canvas.toDataURL();  // Return DataURL
}

Performance Optimization Tips:

  • Reuse Canvas instances to reduce memory overhead.
  • Use offscreen-canvas for high-concurrency scenarios.
  • Set appropriate image quality parameters.

Solution 2: SVG Rendering

Use jsdom to output SVG format, suitable for vector graphics:

const { JSDOM } = require('jsdom');
const echarts = require('echarts');

async function renderSVG(options) {
  const dom = new JSDOM(`<!DOCTYPE html><div id="chart"></div>`);
  const chart = echarts.init(dom.window.document.getElementById('chart'));
  
  chart.setOption(options);
  return chart.renderToSVGString();  // Directly output SVG string
}

Feature Comparison:

Feature Canvas Solution SVG Solution
Output Format Bitmap Vector
File Size Larger Smaller
Rendering Speed Fast Slower
Scaling Effect Pixelated Lossless

Dynamic Data Rendering

Server-side rendering also supports dynamic data updates. A typical workflow:

// Simulate database query
async function fetchData() {
  return {
    xAxis: ['Mon', 'Tue', 'Wed'],
    series: [Math.random() * 100, Math.random() * 100]
  };
}

// Dynamic rendering function
async function renderDynamicChart() {
  const data = await fetchData();
  const canvas = createCanvas(600, 400);
  const chart = echarts.init(canvas);
  
  chart.setOption({
    xAxis: { data: data.xAxis },
    series: [{ data: data.series }]
  });
  
  return canvas.toBuffer();
}

Cluster Rendering Solution

For high-concurrency scenarios, the following architecture is recommended:

Client Request → Load Balancer → Rendering Cluster → Redis Cache → Return Result

Example implementation code:

// Use Koa to build a rendering service
const Koa = require('koa');
const router = require('@koa/router')();
const app = new Koa();

// Add cache middleware
const cache = require('koa-redis-cache');
app.use(cache({
  routes: ['/chart'],
  expire: 3600
}));

// Chart rendering API
router.get('/chart', async (ctx) => {
  const { type = 'png', config } = ctx.query;
  const result = await renderService.render(config, type);
  ctx.body = result;
});

Error Handling Mechanism

A robust error handling system should include the following layers:

  1. Parameter Validation:
function validateOptions(options) {
  if (!options || typeof options !== 'object') {
    throw new Error('Invalid options format');
  }
  // Additional validation rules...
}
  1. Rendering Fallback:
try {
  const result = await renderChart(options);
  if (!result) throw new Error('Empty render result');
  return result;
} catch (err) {
  console.error(`Render failed: ${err.stack}`);
  return fallbackImage;  // Return fallback image
}
  1. Performance Monitoring:
const start = Date.now();
await renderChart(options);
const duration = Date.now() - start;

metrics.timing('chart.render.time', duration);
if (duration > 1000) {
  metrics.increment('chart.render.slow');
}

Practical Application Scenarios

Scenario 1: PDF Report Generation

Process for embedding charts in PDF documents:

const { PDFDocument } = require('pdf-lib');

async function generatePDF() {
  const pdfDoc = await PDFDocument.create();
  const page = pdfDoc.addPage([600, 800]);
  
  // Render chart
  const chartImage = await renderChart(salesOptions);
  const pngImage = await pdfDoc.embedPng(chartImage);
  
  // Insert into PDF
  page.drawImage(pngImage, {
    x: 50,
    y: 500,
    width: 500,
    height: 300
  });
  
  return pdfDoc.save();
}

Scenario 2: Email Template Embedding

Embed dynamic charts in emails:

const nodemailer = require('nodemailer');

async function sendReportEmail() {
  const chartDataURL = await renderChart(emailOptions);
  
  const transporter = nodemailer.createTransport();
  await transporter.sendMail({
    html: `<img src="${chartDataURL}" alt="Monthly Report">`,
    // Other email configurations...
  });
}

Performance Optimization Practices

Memory Management

// Explicitly release resources
function disposeChart(chart) {
  chart.dispose();
  if (chart.getDom()) {
    chart.getDom().remove();
  }
}

// Use WeakMap to cache instances
const chartCache = new WeakMap();
function getCachedChart(options) {
  if (!chartCache.has(options)) {
    chartCache.set(options, initChart(options));
  }
  return chartCache.get(options);
}

Cluster Stress Testing

Use Artillery for load testing:

config:
  target: "http://render-service"
  phases:
    - duration: 60
      arrivalRate: 50
scenarios:
  - flow:
      - get:
          url: "/chart?type=svg"

Typical optimization results:

  • Average response time reduced from 1200ms to 400ms after warm-up.
  • Memory usage reduced by 40% through instance reuse.
  • Throughput increased 3x after horizontal scaling.

Version Compatibility Handling

Handling differences between ECharts versions:

function adaptOptionForVersion(option, version) {
  if (version.startsWith('4.')) {
    // Special handling for v4
    delete option.darkMode;
  } else if (version.startsWith('5.')) {
    // Support for v5 features
    option.aria = option.aria || {};
  }
  return option;
}

Security Measures

  1. Prevent malicious configurations:
function sanitizeOptions(options) {
  // Limit maximum data size
  if (options.dataset && options.dataset.source) {
    if (options.dataset.source.length > 1000) {
      throw new Error('Data exceeds maximum limit');
    }
  }
  
  // Filter dangerous properties
  const forbiddenKeys = ['script', 'javascript'];
  Object.keys(options).forEach(key => {
    if (forbiddenKeys.includes(key.toLowerCase())) {
      delete options[key];
    }
  });
}
  1. Rate limiting:
const rateLimit = require('koa-ratelimit');
app.use(rateLimit({
  driver: 'redis',
  db: redisClient,
  duration: 60000,
  max: 100
}));

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.