sara_123
sara_123

Reputation: 433

D3 : Loading multiple CSV files, and visualizing them using parallel coordinates

I have 4 csv files (say 4 classes), each has 100x200 elements (100 cases, 200 features). I need to visualize some of these features (feature1,feature2,...,feature8; So I will have 100x8 for each csv file) using a parallel coordinates. To recognize the classes, I need to use 4 different color.

I already have the parallel coordinates that I can use to visualize the data (100x10) in one csv file. The code is as below:

    <style type="text/css">

      svg {
        font: 10px sans-serif;
      }

      .background path {
        fill: none;
        stroke: #ccc;
        stroke-opacity: .1;
        shape-rendering: crispEdges;
      }

      .foreground path {
        fill: none;
        stroke: steelblue;
        stroke-opacity: .7;
      }

      .brush .extent {
        fill-opacity: .3;
        stroke: #fff;
        shape-rendering: crispEdges;
      }

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

      .axis text {
        text-shadow: 0 1px 0 #fff;
        cursor: move;
      }

    </style>
  </head>
<body>
<script type="text/javascript">

        var m = [30, 10, 10, 10],
            w = 1300 - m[1] - m[3],
            h = 500 - m[0] - m[2];

        var x = d3.scale.ordinal().rangePoints([0, w], 1),
            y = {},
            dragging = {};

        var line = d3.svg.line(),
            axis = d3.svg.axis().orient("left"),
            background,
            foreground;

     var svg = d3.select("body").append("svg:svg")
        .attr("width", w + m[1] + m[3])
        .attr("height", h + m[0] + m[2])
      .append("svg:g")
        .attr("transform", "translate(" + m[3] + "," + m[0] + ")");

      d3.csv("myData.csv")
      .row(function(d) {
        return {
        // Features that are needed to be visualize
         feature1   :  d.feature1,
         feature2   :  d.feature2,
         feature3   :  d.feature3,
         feature4   :  d.feature4,
         feature5   :  d.feature5,
         feature6   :  d.feature6,
         feature7   :  d.feature7,
         feature8   :  d.feature8
        };
      })
      .get(function(e, data) {
        x.domain(
            dimensions = d3.keys(data[0])
                            .filter(function(d) {
                                        return d != "source" && 
                                                (y[d] = d3.scale.linear()
                                                            .domain(d3.extent(data, function(p) { return +p[d]; }))
                                                            .range([h, 0]));
      }
      ));

      // Add grey background lines for context.
      background = svg.append("svg:g")
          .attr("class", "background")
        .selectAll("path")
          .data(data)
        .enter().append("svg:path")
          .attr("d", path);

      // Add blue foreground lines for focus.
      foreground = svg.append("svg:g")
          .attr("class", "foreground")
        .selectAll("path")
          .data(data)
        .enter().append("svg:path")
          .attr("d", path);

      // Add a group element for each dimension.
      var g = svg.selectAll(".dimension")
          .data(dimensions)
        .enter().append("svg:g")
          .attr("class", "dimension")
          .attr("transform", function(d) { return "translate(" + x(d) + ")"; })
          .call(d3.behavior.drag()
            .on("dragstart", function(d) {
              dragging[d] = this.__origin__ = x(d);
              background.attr("visibility", "hidden");
            })
            .on("drag", function(d) {
              dragging[d] = Math.min(w, Math.max(0, this.__origin__ += d3.event.dx));
              foreground.attr("d", path);
              dimensions.sort(function(a, b) { return position(a) - position(b); });
              x.domain(dimensions);
              g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
            })
            .on("dragend", function(d) {
              delete this.__origin__;
              delete dragging[d];
              transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
              transition(foreground)
                  .attr("d", path);
              background
                  .attr("d", path)
                  .transition()
                  .delay(500)
                  .duration(0)
                  .attr("visibility", null);
            }));

      // Add an axis and title.
      g.append("svg:g")
          .attr("class", "axis")
          .each(function(d) { d3.select(this).call(axis.scale(y[d])); })
        .append("svg:text")
          .attr("text-anchor", "middle")
          .attr("y", -9)
          .text(String);

      // Add and store a brush for each axis.
      g.append("svg:g")
          .attr("class", "brush")
          .each(function(d) { d3.select(this).call(y[d].brush = d3.svg.brush().y(y[d]).on("brush", brush)); })
        .selectAll("rect")
          .attr("x", -8)
          .attr("width", 16);
    });

    function position(d) {
      var v = dragging[d];
      return v == null ? x(d) : v;
    }

    function transition(g) {
      return g.transition().duration(500);
    }

    // Returns the path for a given data point.
    function path(d) {
      return line(dimensions.map(function(p) { return [position(p), y[p](d[p])]; }));
    }

    // Handles a brush event, toggling the display of foreground lines.
    function brush() {
      var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }),
          extents = actives.map(function(p) { return y[p].brush.extent(); });
      foreground.style("display", function(d) {
        return actives.every(function(p, i) {
          return extents[i][0] <= d[p] && d[p] <= extents[i][1];
        }) ? null : "none";
      });
    }

        </script>
      </body>

Also my sample data is something like this:

path,Ktype,label,CGX,CGY,C_1,C_2,C_3,C_4,total1,total2,totalI3,total4,feature1,feature2,feature3,feature4,feature5,feature6,feature7,feature8,feature9,feature10,feature11,feature12,A,B,C,D,feature13,feature14,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
    .\Mydata\Case1,k1,1,42,33,0,57.44534,0,52597,71,16,10,276,4038,3789.631,0.6173469,0.6499337,2.103316,0.6661285,1.065539,248.3694,0.630161,0.000192848,0.9999996,0.000642777,1,0,0,1,9.60E-05,3136.698,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
    .\Mydata\Case2,k1,2,163,29,0,43.28862,0,49050,71,16,10,248,2944,2587.956,0.5726808,0.5681185,2.130387,0.601512,1.137578,356.0444,0.6335613,0.000327267,1.000029,0.001271235,1,0,0,1,0.00010854,2676.418,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
    .\Mydata\Case3,k1,3,774,19,0,45.26291,0,53455,71,16,10,212,2366,1982.547,0.408179,0.4579566,1.994296,0.6615351,1.193415,383.4534,0.7153812,0.000264522,1.000031,0.001210507,1,1,0,0,9.54E-05,3221.289,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
    .\Mydata\Case4,k1,4,1116,25,0,80.76469,0,57542,71,16,10,284,3908,3453.988,0.3549117,0.4811547,1.982244,0.6088744,1.131446,454.0122,0.6166388,0.000314288,0.9999836,0.00129846,0,1,1,0,0.000140592,2143.42,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
    .\Mydata\Case5,k1,5,1364,59,1,52.96776,0,49670,71,16,10,228,2725,2642.675,0.4328255,0.475517,1.859871,0.6587288,1.031152,82.32471,0.5775694,0.000466264,0.9999803,0.001765345,0,1,1,0,0.00012014,2439.636,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

Now, I have two questions:

First, I don't know how I can load 4 csv files at the same time? In future, I might need to increase the number of classes (i.e. increase the number of csv files needed to be visualize), so maybe combining the csv files, and loading just one csv file is not good idea. How I can handle that?

Second, how I should color the lines regarding to the csv file they are belonging to?

Upvotes: 1

Views: 1525

Answers (1)

Oleg
Oleg

Reputation: 9359

To load multiple files asynchronously, you can use a queuing library, such as queue.js.

Then, once the files are loaded, you can render them inside of individual overlapping g group elements with color applied to the entire group based, for example, on the index of the order in which the file was loaded.

Below is an untested and unfinished solution that might give you some clues:

var q = queue(), // create the queue
    dataSources = [ // your data sources
        'dataSource1.csv',
        'dataSource2.csv',
        ...
    ],
    colorScale = d3.scale.category20(); // categorical color scale bundled with d3

// Returns a different color for elements based on their index in the data:
function colorByIndex(d, i) {
    return colorScale(i);
}

// Go through each data source and add it to the queue:
dataSources.forEach(function (source) {
    q.defer(function (callback) {
        d3.csv(source, callback);
    });
});

// Wait for all requests to be completed:
q.awaitAll(function (error, results) {
    // TODO: check for errors!
    // Select and create groups of elements that are to be colored differently:
    d3.selectAll('.group').data(results) // array of arrays
        .append('g')
        .style('color', colorByIndex) // this applies color to the entire group
            .selectAll('???') // whatever you render, paths perhaps
            .data(function (d) { return d; })
            ...
            // at this point the d variable is an array of data
            // straight from each of your csv files, you can
            // proceed rendering as you usually do and there's
            // no longer need to apply any colors
});

Alternatively, you can merge the results together in a single array and mark each individual row with an indicator of which file it comes from, like so:

['dataSource1.csv', 'a1', 'b1', 'c1', ...],
['dataSource1.csv', 'a2', 'b2', 'c2', ...],
['dataSource2.csv', 'a3', 'b3', 'c3', ...],
...

And then apply color based on the value of the indicator.

Upvotes: 3

Related Questions