最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - d3: flatten nested data? - Stack Overflow

programmeradmin3浏览0评论

How do I flatten a table based on a series of nested values, using D3?

For the following cars.json, I wish to use D3 to flatten the hierarchy, providing a new row for each model year of each model. So there should be a total of nine line items, three for each make and model.

I'm sure I'm approaching this wrong, but I'm a bit new at D3, and I don't know how to think about it. I've seen other questions using d3.nest, but as I'm not trying to group anything, it doesn't seem applicable. Thanks!

cars.json

[
  {
    "make": "Ford",
    "model": "Escape",
    "years": [
      {
        "year": 2013,
        "price": 16525
      },
      {
        "year": 2014
      },
      {
        "year": 2015
      }
    ]
  },
  {
    "make": "Kia",
    "model": "Sportage",
    "years": [
      {
        "year": 2012
      },
      {
        "year": 2013,
        "price": 16225
      },
      {
        "year": 2014
      }
    ]
  },
  {
    "make": "Honda",
    "model": "CR-V",
    "years": [
      {
        "year": 2008
      },
      {
        "year": 2009
      },
      {
        "year": 2010,
        "price": 12875
      }
    ]
  }
]

desired output

<table>
    <thead>
        <tr><th>Make</th><th>Model</th><th>Year</th><th>Price</th></tr>
    </thead>
    <tbody>
        <tr><td>Ford</td><td>Escape</td><td>2013</td><td>16525</td></tr>
        <tr><td>Ford</td><td>Escape</td><td>2014</td><td></td></tr>
        <tr><td>Ford</td><td>Escape</td><td>2015</td><td></td></tr>
        <tr><td>Kia</td><td>Sportage</td><td>2012</td><td></td></tr>
        <tr><td>Kia</td><td>Sportage</td><td>2013</td><td>16225</td></tr>
        <tr><td>Kia</td><td>Sportage</td><td>2014</td><td></td></tr>
        <tr><td>Honda</td><td>CR-V</td><td>2008</td><td></td></tr>
        <tr><td>Honda</td><td>CR-V</td><td>2009</td><td></td></tr>
        <tr><td>Honda</td><td>CR-V</td><td>2010</td><td>12875</td></tr>
    </tbody>
</table>

current attempt

<table id="cars_table">
    <thead>
        <th>Make</th><th>Model</th><th>Year</th><th>Price</th>
    </thead>
    <tbody></tbody>
    <tfoot></tfoot>
</table>

<script>
(function(){
    d3.json('/static/cars.json', function(error, cars) {

        var tbody = d3.select('tbody')
        rows = tbody.selectAll('tr').data(cars).enter().append('tr')

        rows.append('td').html(function(d) {
            return d.make
        })

        rows.append('td').html(function(d) {
            return d.model
        })

        var years = rows.append('td').html(function(d) {
            return d.years
            // don't want this nested; probably should be peeled out into another `selectAll`, but I don't know where?
        })
    })
})()
</script>

How do I flatten a table based on a series of nested values, using D3?

For the following cars.json, I wish to use D3 to flatten the hierarchy, providing a new row for each model year of each model. So there should be a total of nine line items, three for each make and model.

I'm sure I'm approaching this wrong, but I'm a bit new at D3, and I don't know how to think about it. I've seen other questions using d3.nest, but as I'm not trying to group anything, it doesn't seem applicable. Thanks!

cars.json

[
  {
    "make": "Ford",
    "model": "Escape",
    "years": [
      {
        "year": 2013,
        "price": 16525
      },
      {
        "year": 2014
      },
      {
        "year": 2015
      }
    ]
  },
  {
    "make": "Kia",
    "model": "Sportage",
    "years": [
      {
        "year": 2012
      },
      {
        "year": 2013,
        "price": 16225
      },
      {
        "year": 2014
      }
    ]
  },
  {
    "make": "Honda",
    "model": "CR-V",
    "years": [
      {
        "year": 2008
      },
      {
        "year": 2009
      },
      {
        "year": 2010,
        "price": 12875
      }
    ]
  }
]

desired output

<table>
    <thead>
        <tr><th>Make</th><th>Model</th><th>Year</th><th>Price</th></tr>
    </thead>
    <tbody>
        <tr><td>Ford</td><td>Escape</td><td>2013</td><td>16525</td></tr>
        <tr><td>Ford</td><td>Escape</td><td>2014</td><td></td></tr>
        <tr><td>Ford</td><td>Escape</td><td>2015</td><td></td></tr>
        <tr><td>Kia</td><td>Sportage</td><td>2012</td><td></td></tr>
        <tr><td>Kia</td><td>Sportage</td><td>2013</td><td>16225</td></tr>
        <tr><td>Kia</td><td>Sportage</td><td>2014</td><td></td></tr>
        <tr><td>Honda</td><td>CR-V</td><td>2008</td><td></td></tr>
        <tr><td>Honda</td><td>CR-V</td><td>2009</td><td></td></tr>
        <tr><td>Honda</td><td>CR-V</td><td>2010</td><td>12875</td></tr>
    </tbody>
</table>

current attempt

<table id="cars_table">
    <thead>
        <th>Make</th><th>Model</th><th>Year</th><th>Price</th>
    </thead>
    <tbody></tbody>
    <tfoot></tfoot>
</table>

<script>
(function(){
    d3.json('/static/cars.json', function(error, cars) {

        var tbody = d3.select('tbody')
        rows = tbody.selectAll('tr').data(cars).enter().append('tr')

        rows.append('td').html(function(d) {
            return d.make
        })

        rows.append('td').html(function(d) {
            return d.model
        })

        var years = rows.append('td').html(function(d) {
            return d.years
            // don't want this nested; probably should be peeled out into another `selectAll`, but I don't know where?
        })
    })
})()
</script>
Share Improve this question edited Mar 7, 2016 at 19:36 allanberry asked Mar 7, 2016 at 18:40 allanberryallanberry 7,7856 gold badges45 silver badges73 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 3

You have to flatten the data before you render it, so that there is one datum per row (and since the rows are not nested the data shouldn't be nested). That way the table-rendering code you showed should just work.

Ideally, you'd transfer the data flat already. CSV lends itself well to transferring flat data, which is often how it es out of relational databases. In your case the columns would be "make", "model", "year" and "price", where each make/model appears 3 times — once per year.

If you can't modify the data then flatten it in JS as soon as it's loaded. I'm nearly sure that there isn't a d3 utility for this (d3.nest() does the opposite of what you're asking to do), but it's simple enough to do this with a loop:

var flatCars = []
cars.forEach(function(car) {
  car.years.forEach(function(carYear) {
    flatCars.push({
      make: car.make,
      model: car.model,
      year: carYear.year,
      price: carYear.price
    });
  });
});

or

var flatCars = cars.reduce(memo, car) {
  return memo.concat(
    car.years.map(function(carYear) {
      return {
        make: car.make,
        model: car.model,
        year: carYear.year,
        price: carYear.price
      }
    });
  );
}, [])

You have to flatten your data before passing it to D3's data() method. D3 should be responsible only for transforming data structure into DOM tree. In other words: use nested data structure if you wish a nested DOM structure.

So, flatten data like this (using lodash here):

data = _.flatten(data.map(function (model) {
    return model.years.map(function (year) {
        return _.assign(year, _.pick(model, 'make', 'model'));
    });
}));

and then pass it to data() method. Working codepen here: http://codepen.io/anon/pen/grPzPJ?editors=1111

发布评论

评论列表(0)

  1. 暂无评论