I am new to D3, and struggled a bit trying to figure out how to use external data source obtained through HTTP REST call as my data source. If you don’t know what I mean by HTTP REST call, it’s basically calling a URL that would return JSON data.

If you are not familiar with D3,  this tutorial by Scott Murray is a required reading. After you are done reading that, read this manual by the author of d3 himself. 

In a nutshell:

  • No, you don’t need jquery library. D3 itself is enough.
  • HTTP calls are asynchronous in d3.
  • If you are pulling external data, you will need to provide a callback function. This callback function will be executed AFTER the HTTP call is done pulling data. The callback function can be a named or anonymous function
  • Need to use d3.json(“some url”, function(error, response)).  The anonymous function is the callback that will be executed when HTTP call is finished.

I will use a working example from nvd3 to accomplish this. You can download the source from nvd3 site. The file I will be using is examples/discreteBar.html

The code below show that data is hardcoded in the original example file from nvd3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script type="text/javascript">
historicalBarChart = [ 
  {
    key: "Cumulative Return",
    values: [
      { 
        "label" : "A" ,
        "value" : 29.765957771107
      } , 
      { 
        "label" : "B" , 
        "value" : 0
      } , 
      { 
        "label" : "C" , 
        "value" : 32.807804682612
      } , 
    ]
  }
];

The code below use the hardcoded data and pass it onto d3 to render.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
nv.addGraph(function() {  
  var chart = nv.models.discreteBarChart()
      .x(function(d) { return d.label })
      .y(function(d) { return d.value })
      .staggerLabels(true)
      //.staggerLabels(historicalBarChart[0].values.length > 8)
      .tooltips(false)
      .showValues(true)
      .transitionDuration(250)
      ;
 
  d3.select('#chart1 svg')
      .datum(historicalBarChart)
      .call(chart);
 
  nv.utils.windowResize(chart.update);
 
  return chart;
});

What we want is to call d3.json function, and perform the following:

  • process the HTTP call and transform the data it into format d3 requires
  • render the graph

The two process I mentioned above requires HTTP call to finish retrieving data before it can be passed to d3. To make http call using d3 library, use:

1
2
3
4
5
6
7
d3.json("http://something.that.returns.json",function(error,response){
//TASK 1
//process http call here. 
 
//TASK 2
//render the graph here
});

This is the most important part: the two task I mentioned previously need to be performed from within the anonymous function because this is the section that will be executed when d3 finishes making http call. The response variable will contain the json data resulting from http call. The error is empty when the http call is successful. It will contain error details otherwise. This is the complete code of calling currency exchange rate api. You need to provide your own api key.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<!DOCTYPE html>
<meta charset="utf-8">
 
<link href="../src/nv.d3.css" rel="stylesheet" type="text/css">
 
<style>
 
body {
  overflow-y:scroll;
  margin: 0;
  padding: 0;
}
 
svg {
  overflow: hidden;
}
 
div {
  border: 0;
  margin: 0;
}
 
 
#test1 {
  margin: 0;
}
 
#chart svg {
  height: 800px;
  width: 600px;
}
 
</style>
 
<body>
 
<div id="offsetDiv">
  <div id="chart" class='with-3d-shadow with-transitions'>
    <svg></svg>
  </div>
</div>
 
<script src="../lib/d3.v3.js"></script>
<script src="../nv.d3.js"></script>
<script src="../src/tooltip.js"></script>
<script src="../src/utils.js"></script>
<script src="../src/models/legend.js"></script>
<script src="../src/models/axis.js"></script>
<script src="../src/models/distribution.js"></script>
<script src="../src/models/scatter.js"></script>
<script src="../src/models/scatterChart.js"></script>
<script>
generateChart();
function generateChart() {
    var chart;
    var rate = [{key:"Exchange Rate against USD", values:[]}];
    var test;
    d3.json('http://openexchangerates.org/api/latest.json?app_id=YOUR_OWN_API', function(error,data){
 
        for(var key in data.rates){
            if(data.rates.hasOwnProperty(key)){
                switch(key) {
                    case "CAD":
                        rate[0]["values"].push({"label":"Canadian Dollar","value":data.rates[key]});
                        break;
                    case "CNY":
                        rate[0]["values"].push({"label":"Chinese Yuan","value":data.rates[key]});
                        break;
                    case "CHF":
                        rate[0]["values"].push({"label":"Swiss Franc","value":data.rates[key]});
                        break;
                    case "EUR":
                        rate[0]["values"].push({"label":"Euro","value":data.rates[key]});
                        break;
                    case "JPY":
                        rate[0]["values"].push({"label":"Japanese Yen","value":data.rates[key]});
                        break;
 
                }
 
            }
        }
        nv.addGraph(function() {
            chart = nv.models.discreteBarChart()
                    .x(function(d) { return d.label })    //Specify the data accessors.
                    .y(function(d) { return d.value })
                    .staggerLabels(true)    //Too many bars and not enough room? Try staggering labels.
                    .tooltips(false)        //Don't show tooltips
                    .showValues(true)       //...instead, show the bar value right on top of each bar.
                    .transitionDuration(350);
 
            generateChart(chart);
            d3.select('#chart svg')
                    .datum(rate)
                    .call(chart);
 
            nv.utils.windowResize(chart.update);
 
            return chart;
        });
    })
 
}
</script>