Server side rendering of Highcharts in .net core

08 Jul 2017

Angular offers server side rendering of Angular components or directives. It can render anything within its Angular context or scope. This helps us to generate the required HTML with the styles. But in case, we have Highcharts inside the app and we want to pre-generate it.

Then, Angular SSR can't help with that, as it doesn't have graphical rendering. Further Highcharts is not Angular Universal compatible because it uses window, document object directly, which are forbidden by Universal Rendering. 

So in order to render Highcharts on server side, Highcharts officially provides a node export server, which can be invoked as a 

  1. Command line tool with arguments - pass chart data as a JSON string or file. Receive the output as a jpg,png,svg or pdf file
  2. Http live server hosted to listen to a port - You can post the chart options to the http://server:port and receive base64 formatted string or a file
  3. Node.js module script - can be run as a node module script, where phantomjs worker can be dynamically spawned and chart could be generated more flexibly.

I had a requirement to receive the output image for a single time use. So using 

Option 1:
Requires external file creations. So it requires write permission for the command line, some kind of network folder to save & expose the generated images to the public network (more work involved)

Option 2:
It can return base64 formatted data, but this requires us to host the Highchart export service on a server, which I don't want to do.

Option 3:
This is more flexible, as I can simply install Microsoft.AspNetCore.NodeServices and invoke the node module script as a long running job/service and when required, I can generate the chart image by invoking the exported node.js javascript module on demand. This allows me to execute node from within the Application server itself without any extra server side setup.

Following is the Highchart export server node.js script  

/**
 * https://github.com/highcharts/node-export-server
 */
const exporter = require('highcharts-export-server');
// Set up a pool of PhantomJS workers
let config = {
  maxWorkers: 10,
  initialWorkers: 5,
  reaper: true,
  listenToProcessExits: true
};
// exporter.logLevel(4);
exporter.initPool(config);

// Export settings 
// var exportSettings = { type: 'png', options: { title: { text: 'My Chart' }, xAxis: { categories: ["Jan", "Feb", "Mar", "Apr", "Mar", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] }, series: [{ type: 'line', data: [1, 3, 2, 4] }, { type: 'line', data: [5, 3, 4, 2] }] } };

var exp = module.exports = {
  generateHighChart: (callback, chartOptions) => {
    try {

      var exportSettings = { type: 'png', options: chartOptions, scale: 2, sourceWidth: '1170', sourceHeight: '300' };

      // Perform an export
      // Export/Chart settings corresponds to the available CLI arguments.
      exporter.export(exportSettings,
        (err, res) => {

          // try {
            // The export result is now in res.
            // If the output is not PDF or SVG, it will be base64 encoded (res.data).
            // If the output is a PDF or SVG, it will contain a filename (res.filename).
            // exporter.killPool();
            // process.exit(1); Immediate exit will cause error in .Net Core NodeServices
          //} catch (innerError) {
          //  exporter.log(4, 'Kill throws error : ' + innerError);
          //}
          callback(err, JSON.stringify(res));
        });
    } catch (err) {
      callback(err, null);
    };
  }
};

// For testing
// exp.generateHighChart(function () { }, exportSettings);

Then you can invoke the node script using ASP .Net core Nodeservices as follows 

using Microsoft.AspNetCore.NodeServices;

public class ChartController : Controller
  {
    private readonly INodeServices _nodeServices;
    public ChartController(INodeServices nodeServices)
    {
      _nodeServices = nodeServices;
    }

    [HttpPost]
    public async Task<IActionResult> Post([FromBody] ChartOptions chartOptions)
    {
      var result = "";
      try
      {
        result = await _nodeServices.InvokeExportAsync<string>("./client/modules/highcharts-image/generate-chart",
          "generateHighChart",
          chartOptions);
      }
      catch (Exception ex)
      {
      }

      return Ok(result);
    }
}

Related Posts