tbone
tbone

Reputation: 5865

D3 example: looks like a javascript variable, but is called like a function

I am trying to understand out how this Javascript code from a D3 example works. The part that doesn't make sense to me are the xScale and yScale "functions" within the buildLine() function.

Taking xScale, it looks like a simple variable that is instantiated to a value:

    var xScale = d3.scale.linear()
                .domain([
                            d3.min(ds.monthlySales, function(d){ return d.month;}) ,
                            d3.max(ds.monthlySales, function(d){ return d.month;})
                        ])                
                .range([0, w])
                .nice(); 

...but then further down within buildLine(), it appears that they are also being called as functions:

    var lineFun = d3.svg.line()
        .x(function (d) {return xScale(d.month); } )
        .y(function (d) {return yScale(d.sales); })
        .interpolate("linear");

More specifically, in:

.x(function (d) {return xScale(d.month); } )

...I'm not understanding where the value of d comes from (and how it has a .month attribute), and most importantly how that is being being received by the xScale "function".

I think there is a fundamental principle of Javascript I need to understand for this code to make sense, but what might that be?


The underlying data can be seen here:

https://github.com/bsullins/d3js-resources/blob/master/monthlySalesbyCategoryMultiple.json

The entire source code:

<!DOCTYPE html>
<html>
    <head>
        <script src="d3.min.js" charset="utf-8"></script>
    </head>
    <body>
    <script>
        var h=100;
        var w=400;
        var padding = 20;             


        //build line
        function buildLine(ds) {
            console.log('xscale-max: '+ d3.max(ds.monthlySales, function (d){ return d.month; }));
            console.log('yscale-max: '+ d3.max(ds.monthlySales, function (d){ return d.sales; }));
            //scales
            var xScale = d3.scale.linear()
                        .domain([
                                    d3.min(ds.monthlySales, function(d){ return d.month;}) ,
                                    d3.max(ds.monthlySales, function(d){ return d.month;})
                                ])                
                        .range([0, w])
                        .nice(); 

            var yScale = d3.scale.linear()
                        .domain([0, d3.max(ds.monthlySales, function(d){ return d.sales;})])
                        .range([h,0])
                        .nice();

            var lineFun = d3.svg.line()
                .x(function (d) {return xScale(d.month); } )
                .y(function (d) {return yScale(d.sales); })
                .interpolate("linear");

            var svg = d3.select("body").append("svg").attr({ width:w, height:h});

            var viz = svg.append("path")
                        .attr({
                            d: lineFun(ds.monthlySales),
                            "stroke" : "purple",
                            "stroke-width": 2,
                            "fill" : "none"
                        });

        }

        //show header
        function showHeader(ds) {
            d3.select("body").append("h1")
                .text(ds.category + " Sales (2013)");
        }


        //get data and draw things  
        d3.json("https://api.github.com/repos/bsullins/d3js-resources/contents/monthlySalesbyCategoryMultiple.json", function(error, data) {

           if(error) {
               console.log(error);
           } else {
               console.log(data); //we're golden!
           }

            var decodedData = JSON.parse(window.atob(data.content));

            console.log(decodedData.contents);


            decodedData.contents.forEach(function(content){
                ds=content;
                console.log(ds);
                showHeader(ds);         
                buildLine(ds);                   
            })

        });  


    </script>
    </body>
</html>

Upvotes: 2

Views: 389

Answers (1)

LeartS
LeartS

Reputation: 2896

Well javascript has first class functions so xScale is a variable that contains a function. Which means you can pass it around and call it.

More specifically, the first code snippet you posted builds a function that accepts a value in the interval specified by .domain([d3.min ..., d3.max ... ]) and returns a value in the interval specified by .range([0, w]). It also says that the value the function returns should be "nice" (.nice()) i.e. truncated to not have several decimals. The function built according to these specs is assigned to the xScale variable.

Later, you use this function in the d3.svg.line generator which is, like d3.scale.linear a function/object that generates (hence, generator) a function, in this case a function that, when provided with a dataset as input, returns a string you can use as the d attribute of the svg path element to draw the path you want. This function is assigned to the variable lineFun (the Fun part!).

The lineFun function, built using the d3.svg.line generator, takes as input a dataset:

lineFun(ds.monthlySales)

and, for each of the datapoints in the dataset, it passes it as input to the accessor functions (that's where the d parameter comes from) so that they can create a x and y value from and for the datapoint. The accessor functions are therefore functions that accept a datapoint and return a value, generally according to some property of the datapoint. So, for example:

function (d) {return xScale(d.month);

Which, given a datapoint d, returns the value the xScale function returns when passing the month attribute of the datapoint as input.

I know I didn't write the most clear explanation, but I hope it can help you a little.

The key points are:

  • in Javascript functions can be assigned to variables and passed around as parameters, just like integers or strings
  • d3.scale.linear and d3.svg.line are generator: they build and return a function
  • d3 uses the shapes generators to return, when given the data as input, the svg attributes that are used to draw paths to represent your data.

From https://github.com/mbostock/d3/wiki/SVG-Shapes#path-data-generators

A path generator, such as that returned by d3.svg.line, is both an object and a function. That is: you can call the generator like any other function, and the generator has additional methods that change its behavior. Like other classes in D3, path generators follow the method chaining pattern where setter methods return the generator itself, allowing multiple setters to be invoked in a concise statement.

Upvotes: 3

Related Questions