Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic loading data using d3.csv in D3 v5

I'm trying to build a dynamically loading bar chart in d3.js which will load data from back-end in parts. Using the d3.csv() function, is there any way to read only first n number of rows from a data for initial draw and then load subsequent data as per my JS logic?

tl;dr I want to selectively access my data inside the d3.csv() function.

I'm trying to run the below code for this :

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

    var loadData = function() {
        d3.csv("test_data.csv", function(data) {
            console.log(data.filter(function(d, i) {
                return i < 2;
            }));

            console.log(data.filter(function(d, i) {
                return i < 3;
            }))
        })
    }

    loadData();

However, I'm getting an error in the console:

Uncaught (in promise) TypeError: data.filter is not a function(…)

Which leads me to believe that this data is not an array. Is this the case or am i facing some other issue here?

Also, how do I access columns (inside csv file) inside this d3.csv function? (if for example, my csv data file contains two columns named a and b).

like image 959
D_S_X Avatar asked Apr 20 '18 12:04

D_S_X


People also ask

Is d3 CSV asynchronous?

d3. csv is asynchronous by design to prevent pages from freezing up, so that can't be changed without changing the d3 library itself.

What does d3 CSV do?

js csv() Function. The d3. csv() function in D3. js is a part of the request API that returns a request for the file of type CSV at the specified URL.

Is the syntax to read JSON data in d3?

Syntax: d3. json(input[, init]);


2 Answers

First, there is no way to load/parse just the first n rows of a CSV with d3.csv, I'm afraid that's not possible. Unfortunately you'll have to load/parse all the file, which may be inconvenient if the file is huge, meaning that the user will have to wait for the whole file to be loaded/parsed before the chart is painted. Also, it's worth mentioning that since d3.csv will load all the file the subsequent filter irrelevant: just use the rows of data you want, don't add even more unnecessary tasks to the browser, just use the rows you want for painting the chart.

Back to your main question:

Your data is an array. The problem here is just that you're using d3.csv as if it was a XHR, which was the case of D3 v4... However, in D3 v5, d3.csv is a promise.

So, it has to be:

d3.csv(url).then(callback);

Have a look at the demo below:

var csv = URL.createObjectURL(new Blob([
  `foo,bar,baz
12,43,21
45,54,21
87,13,17
98,69,17`
]));

d3.csv(csv).then(function(data) {
  console.log(data.filter(function(d, i) {
    return i < 2;
  }));
})
<script src="https://d3js.org/d3.v5.min.js"></script>

Regarding your second question, d3.csv exposes the columns in an array property named columns:

var csv = URL.createObjectURL(new Blob([
  `foo,bar,baz
12,43,21
45,54,21
87,13,17
98,69,17`
]));

d3.csv(csv).then(function(data) {
  console.log("columns are: " + data.columns)
})
<script src="https://d3js.org/d3.v5.min.js"></script>
like image 125
Gerardo Furtado Avatar answered Sep 27 '22 02:09

Gerardo Furtado


One thing to add to Gerardo Furtado's answer: your example is structured like this:

d3.csv('some_file.csv', someFunction)

In d3.csv V5, if a function is passed as an argument like this here, it gets called once for each row, passed an object representing that row, its index, and an array of column keys, allowing the rows to be altered. So, the specific error is because the first arg in this callback is an object representing a row, not an array representing the data set.

Then, when they're all done, the promise is complete and a callback provided with .then(someFunction) is fired.

d3.csv('some_file.csv', transformRow).then(processData)

A transformRow function is optional; if one is provided, then whatever it returns for each row replaces that row in the data.

The processData callback then gets an array of rows, with a property columns which is an array of the original column names (so if transformRow returns an object with different property keys, the data.columns won't match the properties of each row).

So for example:

    var csv = URL.createObjectURL(new Blob([
     `name,start,end
      SOMETHING,123,321
      INVALID,321,123
      ANOTHER,111,333`
    ]));

    d3.csv(csv, processRow).then(processData)
    
    function processRow (row, index, columnKeys) {
      row[columnKeys[0]] = row[columnKeys[0]].trim().toLowerCase()
      row.duration = row.end - row.start // this new property doesn't change data.columns
      if (row.end > row.start) return row
    }
    
    function processData (data) {
      console.log(data, data.columns)
    }
<script src="https://d3js.org/d3.v5.min.js"></script>
like image 44
user56reinstatemonica8 Avatar answered Sep 24 '22 02:09

user56reinstatemonica8