Server-side charts with ASP.NET Core, Node services and D3.js

ASP.NET Core introduces Node services that allow applications to run Node scripts on server. We can send data from controller action to Node script and get back some output we can use in view. This blog post shows how to use Node services to render d3.js charts on server-side to PNG files.

Why Node services

Node has tons of modules available and ready for use. Before Node services there were no good way in ASP.NET Core to run Node scripts on such a generalized way than now. We can install node modules, write our own ones and call these from controllers to get back some data or to get something important done.

Related closely to Node services there are some other services in ASP.NET Core that may interest reader who finds this post useful:

  • SPA services
  • Angular services (in beta)
  • React services (in beta)

For good overview I suggest MSDB blog post Building Single Page Applications on ASP.NET Core with JavaScriptServices.

Adding Node services

To use Node services we have to add reference to NuGet package Microsoft.AspNetCore.NodeServices. After this we need to register Node services so we can use them in controllers through framework-level dependency injection. This is done in ConfigureServices() method of Startup class.


public void ConfigureServices(IServiceCollection services)
{
    services.AddNodeServices();
    services.AddMvc();
}

Now Node services are ready for use.

Initializing chart

Let’s initialize chart from MVC controller. For this let’s add new action called Chart to Home controller. Here is the code.


public async Task<IActionResult> Chart([FromServices] INodeServices nodeServices)
{
    var options = new { width = 400, height = 200 };

    var data = new[] {
        new { label = "Abulia", count = 10 },
        new { label = "Betelgeuse", count = 20 },
        new { label = "Cantaloupe", count = 30 },
        new { label = "Dijkstra", count = 40 }
    };

    ViewData["ChartImage"] = await nodeServices.InvokeAsync<string>("NodeChart.js", options, data);

    return View();
}

We create anonymous objects for options and chart data. We can use options defined here to be dynamic and configurable by user. Chart data here is static in sample purposes but in real application there will be query to some data source where chart data is stored. To render chart we use instance of INodeServices to call out Node script. Notice how options and data are given to Node script.

Displaying chart on view

Before jumping to JavaScript let’s define simple view to display our chart.


@{
    ViewData["Title"] = "Chart";
}
<h2>@ViewData["Title"]</h2>

<p><img src="@Html.Raw(ViewData["ChartImage"])" /></p>

One hint can be read out from view: chart image will be inline image.

Installing Node modules

In this point I expect that Node is installed to box where web application runs and it is configured correctly. Before we can write our Node script we have to install all modules we use.

  1. Open command line and move to web application root folder (not under wwwroot)
  2. Run npm install –save svg2png
  3. Run npm install –save jsdom
  4. Run npm install –save d3

Ignore errors and warning about packages.json file as things work still well.

Building chart and converting SVG to PNG

Now let’s write script that generates chart and transforms it to PNG image. For this let’s add file called NodeChart.js to root folder of our web application.

Here is the script with some code comments to clarify what it does. In short, we create disconnected HTML DOM, attach it to D3, generate chart and then convert SVG to PNG. After this we return PNG as inline image back to controller.


// Include all modules we need
const svg2png = require("svg2png");
const { JSDOM } = require("jsdom");
const d3 = require('d3');

// Define module
// callback - function to return data to caller
// options - chart options defined in controller
// data - chart data coming from controller
module.exports = function(callback, options, data) {

    // Create disconnected HTML DOM and attach it to D3
    var dom = new JSDOM('<html><body><div id="chart"></div></html>');
    dom.window.d3 = d3.select(dom.window.document);

    // Build D3 chart
    var width = options.width || 360;
    var height = options.height || 360;
    var radius = Math.min(width, height) / 2;

    var color = d3.scaleOrdinal(d3.schemeCategory20b);

    var svg = dom.window.d3.select('#chart')
        .append('svg')
        .attr('width', width)
        .attr('height', height)
        .append('g')
        .attr('transform', 'translate(' + (width / 2) +
        ',' + (height / 2) + ')');

    var arc = d3.arc()
        .innerRadius(0)
        .outerRadius(radius);

    var pie = d3.pie()
        .value(function(d) { return d.count; })
        .sort(null);

    var path = svg.selectAll('path')
        .data(pie(data))
        .enter()
        .append('path')
        .attr('d', arc)
        .attr('fill', function(d) {
            return color(d.data.label);
        });

    // Convert SVG to PNG and return it to controller
    var svgText = dom.window.d3.select('#chart').html();
    svg2png(Buffer.from(svgText), { width: width, height: height })
        .then(buffer => 'data:image/png;base64,' + buffer.toString('base64'))
        .then(buffer => callback(null, buffer));
};

Here is the chart view with simple pie chart that was built in Node on server.

ASP.NET Core server-side chart using Node services and D3.js

The code above can be used also as a starting point for some more complex server-side chart.

There are also other ways to get SVG to image (SVG => Canvas => data URI) but the method used here is easy and powerful enough. It doesn’t need any additional software to be installed on server and therefore it is less demanding about technical environment.

References



See also

One thought on “Server-side charts with ASP.NET Core, Node services and D3.js

Leave a Reply

Your email address will not be published. Required fields are marked *