Pablo EM
Pablo EM

Reputation: 6689

Updating data in a simple reusable D3 chart

In the book "Data Visualization with D3 Cookbook" (chapter 8) appears an example where the author plots two lines. Basically, the data is created randomly:

var numberOfSeries = 2,
    numberOfDataPoint = 11,
    data = [];

for (var i = 0; i < numberOfSeries; ++i)
    data.push(d3.range(numberOfDataPoint).map(function (i) {
        return {x: i, y: randomData()};
    }));

Then, the author creates an instance of the chart and defines the scales' domain:

var chart = lineChart()
        .x(d3.scale.linear().domain([0, 10]))
        .y(d3.scale.linear().domain([0, 10]));

After that, the data is introduced using the addSeries() method:

data.forEach(function (series) {
    chart.addSeries(series);
});

And finally, the chart is rendered calling the render() method:

chart.render();

Additionally, the plot has an Update buttom that allows to update the data showed by calling this function:

function update() {
    for (var i = 0; i < data.length; ++i) {
        var series = data[i];
        series.length = 0;
        for (var j = 0; j < numberOfDataPoint; ++j)
            series.push({x: j, y: randomData()});
    }

    chart.render();
}

My problem is that I can't understand how the data is updated in the plot without calling the addSeries() method again, after press the Update button, i.e., in the update() function. The data internally is stored in the variable _data, which can be modified with the method addSeries(). Any ideas?

Many thanks!

The complete code is here, but following I've copied the most important parts:

function lineChart() { // <-1A
    var _chart = {};

    var _width = 600, _height = 300, // <-1B
            _margins = {top: 30, left: 30, right: 30, bottom: 30},
            _x, _y,
            _data = [],
            _colors = d3.scale.category10(),
            _svg,
            _bodyG,
            _line;

    _chart.render = function () { // <-2A
        if (!_svg) {
            _svg = d3.select("body").append("svg") // <-2B
                    .attr("height", _height)
                    .attr("width", _width);

            renderAxes(_svg);

            defineBodyClip(_svg);
        }

        renderBody(_svg);
    };

    // Axis rendering functions ...


    function renderBody(svg) { // <-2D
        if (!_bodyG)
            _bodyG = svg.append("g")
                    .attr("class", "body")
                    .attr("transform", "translate(" 
                        + xStart() + "," 
                        + yEnd() + ")") // <-2E
                    .attr("clip-path", "url(#body-clip)");        

        renderLines();

    }

    function renderLines() {
        _line = d3.svg.line() //<-4A
                        .x(function (d) { return _x(d.x); })
                        .y(function (d) { return _y(d.y); });

        _bodyG.selectAll("path.line")
                    .data(_data)
                .enter() //<-4B
                .append("path")                
                .style("stroke", function (d, i) { 
                    return _colors(i); //<-4C
                })
                .attr("class", "line");

        _bodyG.selectAll("path.line")
                .data(_data)
                .transition() //<-4D
                .attr("d", function (d) { return _line(d); });
    }

    //Some getter/setters functions and other stuff

    _chart.addSeries = function (series) { // <-1D
        _data.push(series);
        return _chart;
    };

    return _chart; // <-1E
}

function randomData() {
    return Math.random() * 9;
}

function update() {
    for (var i = 0; i < data.length; ++i) {
        var series = data[i];
        series.length = 0;
        for (var j = 0; j < numberOfDataPoint; ++j)
            series.push({x: j, y: randomData()});
    }

    chart.render();
}

var numberOfSeries = 2,
    numberOfDataPoint = 11,
    data = [];

for (var i = 0; i < numberOfSeries; ++i)
    data.push(d3.range(numberOfDataPoint).map(function (i) {
        return {x: i, y: randomData()};
    }));

var chart = lineChart()
        .x(d3.scale.linear().domain([0, 10]))
        .y(d3.scale.linear().domain([0, 10]));

data.forEach(function (series) {
    chart.addSeries(series);
});

chart.render();

Upvotes: 0

Views: 65

Answers (1)

Lars Kotthoff
Lars Kotthoff

Reputation: 109292

The update() function is modifying each series in place, which is picked up automatically by the render() function. This works because the closure stores a reference to the series (which is modified) rather than the actual values.

What happens is the same as when you would modify a global variable. The addSeries() function adds a new series, but in this particular case you're not adding any new series, but modifying the existing ones.

That said, I wouldn't consider the code to be particularly well designed.

Upvotes: 1

Related Questions