D3js take data from an array instead of a file

45,661

Solution 1

The D3 gallery has a lot of good examples, but a lot of the examples loads their data from a tab-separated values file. This is a nice way to separate data and visualization in short examples, but it can be a bit confusing if you are new to D3 and JavaScript, since it requires some basic knowledge about both d3.tsv() and callback functions to understand what is going on.

Disclaimer: The following sections will give some very simplified explanations.

What is d3.tsv() doing?

d3.tsv() is basically responsible for loading the data from data.tsv, parse it into a variable called data, and send this variable to a callback function.

d3.tsv("data.tsv", type, function(error, data) { <- This is the callback function!
  // This code is executed when the data.tsv file is loaded.
});

As soon as the data is loaded, it is sent as the data argument to the function. Then the code inside the callback function is executed. Since we don't need the callback function for anything else than as an argument to the d3.tsv() function, we make it directly as an anonymous function, instead of giving it a name like usual.

This kind of use of callback and anonymous functions is very typical of JavaScript, and is well worth reading up on. Understand JavaScript Callback Functions and Use Them and Understanding JavaScript Callbacks should get you started.

Let's see how we can use this information to rewrite our code.

How can I rewrite the example to use data from a local variable?

First we have to make variable containing our data. Let's call it "data", the same as in our callback function, and give it the values from the example.

var data = [
  {letter: "A", frequency: .08167},
  {letter: "B", frequency: .01492},
  {letter: "C", frequency: .02780},
  {letter: "D", frequency: .04253},
  {letter: "E", frequency: .12702},
  {letter: "F", frequency: .02288},
  {letter: "G", frequency: .02022},
  {letter: "H", frequency: .06094},
  {letter: "I", frequency: .06973},
  {letter: "J", frequency: .00153},
  {letter: "K", frequency: .00747},
  {letter: "L", frequency: .04025},
  {letter: "M", frequency: .02517},
  {letter: "N", frequency: .06749},
  {letter: "O", frequency: .07507},
  {letter: "P", frequency: .01929},
  {letter: "Q", frequency: .00098},
  {letter: "R", frequency: .05987},
  {letter: "S", frequency: .06333},
  {letter: "T", frequency: .09056},
  {letter: "U", frequency: .02758},
  {letter: "V", frequency: .01037},
  {letter: "W", frequency: .02465},
  {letter: "X", frequency: .00150},
  {letter: "Y", frequency: .01971},
  {letter: "Z", frequency: .00074}
];

Put this variable somewhere before the call to d3.tsv, as the code in the callback function is dependent on this variable.

I have choose to represent the data as a list of objects with a letter and frequency property. This is an easy way to do it, since it closely resembles the way d3.tsv() would parse the .tsv file. This means that we don't have to change the code in the callback function, since it already expects a variable with data in this format. You can change this if you like, but remember to change how the callback code uses the "data" variable.

Now we can remove the code related to the d3.tsv call, just leaving the code contained in the callback function. So this code:

d3.tsv("data.tsv", type, function(error, data) {
  x.domain(data.map(function(d) { return d.letter; }));
  // code omitted.
  .on('mouseout', tip.hide)
});

Becomes this code:

x.domain(data.map(function(d) { return d.letter; }));
// code omitted.
.on('mouseout', tip.hide)

Now the example should be working fine. You can play around with this strategy for rewriting other examples in the D3 gallery as well.

Finally, I have included the code for the new index.html file. A working example can be found at JSFiddle.

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
  font: 10px sans-serif;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.bar {
  fill: orange;
}

.bar:hover {
  fill: orangered ;
}

.x.axis path {
  display: none;
}

.d3-tip {
  line-height: 1;
  font-weight: bold;
  padding: 12px;
  background: rgba(0, 0, 0, 0.8);
  color: #fff;
  border-radius: 2px;
}

/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
  box-sizing: border-box;
  display: inline;
  font-size: 10px;
  width: 100%;
  line-height: 1;
  color: rgba(0, 0, 0, 0.8);
  content: "\25BC";
  position: absolute;
  text-align: center;
}

/* Style northward tooltips differently */
.d3-tip.n:after {
  margin: -1px 0 0 0;
  top: 100%;
  left: 0;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<script>

var margin = {top: 40, right: 20, bottom: 30, left: 40},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var formatPercent = d3.format(".0%");

var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1);

var y = d3.scale.linear()
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .tickFormat(formatPercent);

var tip = d3.tip()
  .attr('class', 'd3-tip')
  .offset([-10, 0])
  .html(function(d) {
    return "<strong>Frequency:</strong> <span style='color:red'>" + d.frequency + "</span>";
  })

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

svg.call(tip);

// The new data variable.
var data = [
  {letter: "A", frequency: .08167},
  {letter: "B", frequency: .01492},
  {letter: "C", frequency: .02780},
  {letter: "D", frequency: .04253},
  {letter: "E", frequency: .12702},
  {letter: "F", frequency: .02288},
  {letter: "G", frequency: .02022},
  {letter: "H", frequency: .06094},
  {letter: "I", frequency: .06973},
  {letter: "J", frequency: .00153},
  {letter: "K", frequency: .00747},
  {letter: "L", frequency: .04025},
  {letter: "M", frequency: .02517},
  {letter: "N", frequency: .06749},
  {letter: "O", frequency: .07507},
  {letter: "P", frequency: .01929},
  {letter: "Q", frequency: .00098},
  {letter: "R", frequency: .05987},
  {letter: "S", frequency: .06333},
  {letter: "T", frequency: .09056},
  {letter: "U", frequency: .02758},
  {letter: "V", frequency: .01037},
  {letter: "W", frequency: .02465},
  {letter: "X", frequency: .00150},
  {letter: "Y", frequency: .01971},
  {letter: "Z", frequency: .00074}
];

// The following code was contained in the callback function.
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);

svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
  .append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", 6)
    .attr("dy", ".71em")
    .style("text-anchor", "end")
    .text("Frequency");

svg.selectAll(".bar")
    .data(data)
  .enter().append("rect")
    .attr("class", "bar")
    .attr("x", function(d) { return x(d.letter); })
    .attr("width", x.rangeBand())
    .attr("y", function(d) { return y(d.frequency); })
    .attr("height", function(d) { return height - y(d.frequency); })
    .on('mouseover', tip.show)
    .on('mouseout', tip.hide)

function type(d) {
  d.frequency = +d.frequency;
  return d;
}

</script>

Solution 2

The whole d3.tsv() stuff (or d3.json() or whatever) is optional. So just strip it with its callback and use your data directly:

var fruits = ['apple', 'mango', 'banana', 'orange'];
d3.select('ul')
    .selectAll('li')
    .data(fruits)
    .enter()
    .append('li')
    .text(function(d) { return d; });

PS: Thats why you will not find a d3.data() function or whatever, I was searching it for a while ;-)

Share:
45,661

Related videos on Youtube

Jim Blum
Author by

Jim Blum

Updated on February 20, 2020

Comments

  • Jim Blum
    Jim Blum about 4 years

    I have found this excellent d3js chart here. However in my case I want this chart to take value from an array instead of a tsv file. I want to make it to take values from a table []. How I can do that? because it uses a function for that, and I do not know where I should put my array.

    Using d3-tip to add tooltips to a d3 bar chart.
    <!DOCTYPE html>
    <meta charset="utf-8">
    <style>
    
    body {
      font: 10px sans-serif;
    }
    
    .axis path,
    .axis line {
      fill: none;
      stroke: #000;
      shape-rendering: crispEdges;
    }
    
    .bar {
      fill: orange;
    }
    
    .bar:hover {
      fill: orangered ;
    }
    
    .x.axis path {
      display: none;
    }
    
    .d3-tip {
      line-height: 1;
      font-weight: bold;
      padding: 12px;
      background: rgba(0, 0, 0, 0.8);
      color: #fff;
      border-radius: 2px;
    }
    
    /* Creates a small triangle extender for the tooltip */
    .d3-tip:after {
      box-sizing: border-box;
      display: inline;
      font-size: 10px;
      width: 100%;
      line-height: 1;
      color: rgba(0, 0, 0, 0.8);
      content: "\25BC";
      position: absolute;
      text-align: center;
    }
    
    /* Style northward tooltips differently */
    .d3-tip.n:after {
      margin: -1px 0 0 0;
      top: 100%;
      left: 0;
    }
    </style>
    <body>
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
    <script>
    
    var margin = {top: 40, right: 20, bottom: 30, left: 40},
        width = 960 - margin.left - margin.right,
        height = 500 - margin.top - margin.bottom;
    
    var formatPercent = d3.format(".0%");
    
    var x = d3.scale.ordinal()
        .rangeRoundBands([0, width], .1);
    
    var y = d3.scale.linear()
        .range([height, 0]);
    
    var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom");
    
    var yAxis = d3.svg.axis()
        .scale(y)
        .orient("left")
        .tickFormat(formatPercent);
    
    var tip = d3.tip()
      .attr('class', 'd3-tip')
      .offset([-10, 0])
      .html(function(d) {
        return "<strong>Frequency:</strong> <span style='color:red'>" + d.frequency + "</span>";
      })
    
    var svg = d3.select("body").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
      .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
    svg.call(tip);
    
    d3.tsv("data.tsv", type, function(error, data) {
      x.domain(data.map(function(d) { return d.letter; }));
      y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
    
      svg.append("g")
          .attr("class", "x axis")
          .attr("transform", "translate(0," + height + ")")
          .call(xAxis);
    
      svg.append("g")
          .attr("class", "y axis")
          .call(yAxis)
        .append("text")
          .attr("transform", "rotate(-90)")
          .attr("y", 6)
          .attr("dy", ".71em")
          .style("text-anchor", "end")
          .text("Frequency");
    
      svg.selectAll(".bar")
          .data(data)
        .enter().append("rect")
          .attr("class", "bar")
          .attr("x", function(d) { return x(d.letter); })
          .attr("width", x.rangeBand())
          .attr("y", function(d) { return y(d.frequency); })
          .attr("height", function(d) { return height - y(d.frequency); })
          .on('mouseover', tip.show)
          .on('mouseout', tip.hide)
    
    });
    
    function type(d) {
      d.frequency = +d.frequency;
      return d;
    }
    
    </script>
    

    Thanks a lot

    • royhowie
      royhowie about 10 years
      What about a JSON file? Does it have to be an array? If so, just store the frequencies in an array of size 26 and each letter in another array. Then, just cross check arrays when you go to make each bar (so that you can label).
    • Jim Blum
      Jim Blum about 10 years
      Thanks a lot @Luxelin for your comment. I want to pass the data from C# to javascript, and therefore I need to be an array ...
    • AmeliaBR
      AmeliaBR about 10 years
      The expected format for most d3 methods is a Javascript array, so if you are using methods that can pass the data from C# to javascript, that should work. Otherwise, if you can write the data to file OR a string as either a JSON array or as a delimited table, then it can be parsed. But it really depends on how you're planning on getting the data from your C# program (I'm assuming on a server) and the browser running Javascript. Give us more info about your data format, and we can tell you how to parse it...
  • Samitha Chathuranga
    Samitha Chathuranga over 8 years
    nice explanation!! can you tell me, what are the changes that we need to do when we need to design grouped bar chart with javascript array data set?