D3js take data from an array instead of a file

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


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 ;-)