this doc on github

Visualizing data using d3js

This is a work in progress. It doesn't work yet in Binder because it relies on HTTP communication between the kernel and the Jupyter frontend.

This notebooks uses directly d3.js library to perform custom data visualisation.

In [ ]:
var rnd = new Random();
var a = Enumerable.Range(1,rnd.Next(4,12)).Select( t => rnd.Next(t, t*10)).ToArray();

Using RequireJS we import d3.js. We setup the rendering code, some SVG filter inspiredy by Visual Cinnamon article on gooey effect.

Using interactive.csharp.getVariable we fetch the variable a value.

In [ ]:
#!js

if (typeof (notebookScope.interval) !== 'undefined') {
    clearInterval(notebookScope.interval);
}

notebookScope.plot = (sgvSelector, variableName) => {
    let dtreeLoader = interactive.configureRequire({
        paths: {
            d3: "https://d3js.org/d3.v6.min"
        }
    });
    dtreeLoader(["d3"], function (d3) {
        let svg = d3.
            select(sgvSelector);
        svg.selectAll("defs").remove();
        svg.selectAll("g").remove();

        let defs = svg.append("defs");

        let filter = defs.append("filter").attr("id", "gooeyCodeFilter");

        filter.append("feGaussianBlur")
            .attr("in", "SourceGraphic")
            .attr("stdDeviation", "10")
            .attr("color-interpolation-filters", "sRGB")
            .attr("result", "blur");

        filter.append("feColorMatrix")
            .attr("in", "blur")
            .attr("mode", "matrix")
            .attr("values", "1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 19 -9")
            .attr("result", "gooey");

        let container = d3
            .select(sgvSelector)
            .append("g")
            .style("filter", "url(#gooeyCodeFilter)");


        console.log(container);

        let updateD3Rendering = () => interactive.csharp.getVariable(variableName)
            .then(data => {
                var i = 0;
                var p = container
                    .selectAll(".points")
                    .data(data, (d, i) => i);

                p.transition()
                    .duration(2000)
                    .style("fill", d => d3.interpolateTurbo(d / 80))
                    .attr("r", d => Math.max(0, d));

                p.enter()
                    .append("circle")
                    .attr("class", "points")
                    .attr("cy", 80)
                    .attr("cx", (d,i) => ((i) + 1) * 60)
                    .transition()
                    .duration(2000)
                    .style("fill", d => d3.interpolateTurbo(d / 80))
                    .ease(d3.easeElasticOut.period(1.00))
                    .attr("r", d => Math.max(0, d)),

                    p.exit()
                        .transition()
                        .duration(1000)
                        .attr("r", 0)
                        .remove();
            });
        notebookScope.interval = setInterval(() => updateD3Rendering(), 3000);
    });
}

Notice the setInterval call near the end of the previous cell. This rechecks the data in the kernel and updates the plot.

Back on the kernel, we can now update the data so that the kernel can see it.

Yes, this is a contrived example, and we're planning to support true streaming data, but it's a start.

In [ ]:
#!html
<svg id="dataPlot1" width="100%" height=200></svg>

#!js
notebookScope.plot("svg#dataPlot1", "a");
In [ ]:
#!csharp
for(var i = 0; i < 10; i++){
    await Task.Delay(1000);
    var limit = rnd.Next(4,12);
    a = Enumerable.Range(1,limit).Select( t => rnd.Next(30, 80)).ToArray();
}
In [ ]: