D3.js 中堆叠图的不同分层的想法

Ideas for distinct layering of stacked graph in D3.js

我有难民在德国寻求庇护的第一次和之后的数据,因此有两张看起来像这样的图表

第一个

第二个

我想从一个过渡到另一个应该不是问题,但我想要的是在一张图中也显示它们。因此,每个国家/地区的两个数据集的组合。但是,如果我将国家与第一个和后面的国家("erst/folge")结合起来,是否可以相互区分?每个国家应该有一层分成两层,一个是这个国家的第一次尝试,另一个是第二次尝试。区分这些子层的一个想法可能是我现在尝试的自定义 coloscale。如何在一张图中显示这两个数据?是否有可能加入两个数据集并仍然看到差异?

这是我的 html:

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

body {
  font: 10px sans-serif;
}

.chart {
  background: #fff;
}

p {
  font: 12px helvetica;
}


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

button {
  position: absolute;
  right: 50px;
  top: 10px;
}

</style>
<body>
<script src="http://d3js.org/d3.v2.js"></script>
<div id="option">
    <input name="updateButton"
           type="button"
           value="Show Data for second Attemp"
           onclick="updateSecond('folgeantraege_monatlich_2015_mitentscheidungenbisnovember.csv')" />

</div>

<div id="option">
    <input name="updateButton"
           type="button"
           value="Show Data for first Attemp"
           onclick="updateFirst('erstantraege_monatlich_2015_mitentscheidungenbisnovember.csv')" />

</div>

<div id="option">
    <input name="updateButton"
           type="button"
           value="Show both"
           onclick="updateBoth('erstantraege_monatlich_2015_mitentscheidungenbisnovember.csv', 'folgeantraege_monatlich_2015_mitentscheidungenbisnovember.csv')" />

</div>

<div class="chart">
</div>

<script>

var margin = {top: 20, right: 40, bottom: 30, left: 30};
var width = document.body.clientWidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;

var x = d3.time.scale()
    .range([0, width]);

var y = d3.scale.linear()
    .range([height-10, 0]);
var dateParser = d3.time.format("%Y-%m-%d").parse;

var stack = d3.layout.stack()
    .offset("zero")
    .values(function(d) { return d.values; })
    .x(function(d) { return d.date; })
    .y(function(d) { return d.value; });

var area = d3.svg.area()
    .interpolate("cardinal")
    .x(function(d) { return x(d.date); })
    .y0(function(d) { return y(d.y0); })
    .y1(function(d) { return y(d.y0 + d.y); });
var z = d3.scale.category20()

chart("erstantraege_monatlich_2015_mitentscheidungenbisnovember.csv");

var datearray = [];
var colorrange = [];

function chart(csvpath) {

// var dateParser = d3.time.format("%Y-%m-%d").parse;
// var margin = {top: 20, right: 40, bottom: 30, left: 30};
// var width = document.body.clientWidth - margin.left - margin.right;
// var height = 400 - margin.top - margin.bottom;

// var x = d3.time.scale()
//     .range([0, width]);
//
// var y = d3.scale.linear()
//     .range([height-10, 0]);

// var z = d3.scale.category20()

//var color = d3.scale.category10()

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

var yAxis = d3.svg.axis()
    .scale(y);

var yAxisr = d3.svg.axis()
    .scale(y);

// var stack = d3.layout.stack()
//     .offset("zero")
//     .values(function(d) { return d.values; })
//     .x(function(d) { return d.date; })
//     .y(function(d) { return d.value; });

var nest = d3.nest()
    .key(function(d) { return d.Land});
 // var nestFiltered = nest.filter(function(d){
 //     return d.Land != 'Total';
 //    })

// var area = d3.svg.area()
//     .interpolate("cardinal")
//     .x(function(d) { return x(d.date); })
//     .y0(function(d) { return y(d.y0); })
//     .y1(function(d) { return y(d.y0 + d.y); });

var svg = d3.select(".chart").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 + ")");

  d3.csv(csvpath, function(data) {
    data.forEach(function(d) {
      d.date = dateParser(d.Datum);
      d.value = +d.ErstanträgeZahl;
  });

 //onsole.log(data);
  var layers = stack(nest.entries(data));
  console.log(nest.entries(data));

  x.domain(d3.extent(data, function(d) { return d.date; }));
  y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);

  svg.selectAll(".layer")
      .data(layers)
      .enter().append("path")
      .attr("class", "layer")
      .attr("d", function(d) { return area(d.values); })
      .style("fill", function(d, i) { return z(i); });



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

  svg.append("g")
      .attr("class", "y axis")
      .attr("transform", "translate(" + width + ", 0)")
      .call(yAxis.orient("right"));

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis.orient("left"));

});
}

function updateSecond(csvpath) {
  var nest = d3.nest()
      .key(function(d) { return d.Land});

  d3.csv(csvpath, function(data) {
    data.forEach(function(d) {
      d.date = dateParser(d.Datum);
      d.value = +d.Summe;
      console.log(d.date);
      console.log(d.value);
    });
    var layers = stack(nest.entries(data));

    x.domain(d3.extent(data, function(d) { return d.date; }));
    y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
    d3.selectAll("path")
        .data(layers)
        .transition()
        .duration(750)
        .style("fill", function(d, i) { return z(i); })
        .attr("d", function(d) { return area(d.values); });
    svg.select(".y.axis") // change the y axis
        .duration(750)
        .call(yAxis);


  });




}

function updateFirst(csvpath) {
  var nest = d3.nest()
      .key(function(d) { return d.Land});

  d3.csv(csvpath, function(data) {
    data.forEach(function(d) {
      d.date = dateParser(d.Datum);
      d.value = +d.ErstanträgeZahl;
      console.log(d.date);
      console.log(d.value);
    });
    var layers = stack(nest.entries(data));

    x.domain(d3.extent(data, function(d) { return d.date; }));
    y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
    d3.selectAll("path")
        .data(layers)
        .transition()
        .duration(750)
        .style("fill", function(d, i) { return z(i); })
        .attr("d", function(d) { return area(d.values); });
    svg.select(".y.axis") // change the y axis
        .duration(750)
        .call(yAxis);



  });
}
function updateBoth(csvpathFirst, csvpathSecond){
  var nest = d3.nest()
      .key(function(d) { return d.Land});

  d3.csv(csvpathFirst, function(data1) {
    d3.csv(csvpathSecond, function(data2) {
      
    });

  });


}
</script>

我的数据在我的 repo

编辑1: 例如 csv1 包含

Datum,Land,Summe,Position,Antragsart,EntscheidungenInsgesamt,Asylberechtigt,Flüchtling,GewährungVonSubsidiäremSchutz,Abschiebungsverbot,UnbegrenzteAblehnungen,Ablehnung,sonstigeVerfahrenserledigungen
2015-01-01,Afghanistan,1129,5,Erst,418,0,105,4,58,66,6,179
2015-02-01,Afghanistan,969,5,Erst,849,9,186,16,100,131,10,397
2015-03-01,Afghanistan,885,5,Erst,1376,17,309,58,158,201,11,622
2015-04-01,Afghanistan,1119,6,Erst,1838,21,384,75,202,261,15,880
2015-05-01,Afghanistan,1151,6,Erst,2272,21,499,91,249,303,16,1093
2015-06-01,Afghanistan,2051,6,Erst,2911,23,683,132,313,377,19,1364
2015-07-01,Afghanistan,2104,6,Erst,3340,27,767,160,366,431,21,1568
2015-08-01,Afghanistan,2270,5,Erst,3660,28,922,172,409,453,23,1653
2015-09-01,Afghanistan,2724,4,Erst,4057,36,1049,201,455,475,26,1815
2015-10-01,Afghanistan,3770,4,Erst,4540,37,1188,234,516,538,29,1998
2015-11-01,Afghanistan,4929,0,Erst,5026,46,1340,253,620,623,49,2095

并且 csv2 包含

Datum,Antragsart,Land,Summe,Position,Datum2,Position,Herkunft,Entscheidungeninsgesamt,Asylberechtigt,Prozent,Flüchtling,Pronzent,GewährungvonsubisdiäremSchutz,Prozent,Abschiebungsverbot,Prozent,UnbegrenzteAblehnungen,Prozent,Ablehnung,Prozent,keinweiteresverfahren,Prozent,sonstigeVerfahrenserledigungen,Prozent
2015-01-01,Folge,Afghanistan,33,10,2015-01-01,10,Afghanistan,29,0,0,5,17.2,2,6.9,8,27.6,0,0,0,0,1,3.4,13,44.8
2015-02-01,Folge,Afghanistan,29,10,2015-02-01,10,Afghanistan,81,0,0,13,16,4,4.9,22,27.2,0,0,0,0,10,12.3,32,39.5
2015-03-01,Folge,Afghanistan,41,9,2015-03-01,9,Afghanistan,135,0,0,21,15.6,10,7.4,37,27.4,1,0.7,0,0,23,17,43,31.9
2015-04-01,Folge,Afghanistan,25,10,2015-04-01,10,Afghanistan,165,0,0,34,20.6,12,7.3,41,24.8,4,2.4,0,0,30,18.2,44,26.7
2015-05-01,Folge,Afghanistan,37,9,2015-05-01,9,Afghanistan,212,0,0,54,25.5,12,5.7,50,23.6,4,1.9,0,0,32,15.1,60,28.3
2015-06-01,Folge,Afghanistan,35,9,2015-06-01,9,Afghanistan,261,0,0,72,27.6,17,6.5,59,22.6,6,2.3,0,0,35,13.4,72,27.6
2015-07-01,Folge,Afghanistan,35,9,2015-07-01,9,Afghanistan,288,0,0,82,28.5,17,5.9,64,22.2,6,2.1,0,0,42,14.6,77,26.7
2015-08-01,Folge,Afghanistan,34,9,2015-08-01,9,Afghanistan,321,0,0,100,31.2,20,6.2,66,20.6,6,1.9,0,0,52,16.2,77,24
2015-09-01,Folge,Afghanistan,27,4,2015-09-01,9,Afghanistan,354,0,0,120,33.9,20,5.6,72,20.3,7,2,0,0,54,15.3,81,22.9
2015-10-01,Folge,Afghanistan,24,9,2015-10-01,9,Afghanistan,389,0,0,136,35,20,5.1,83,21.3,7,1.8,0,0,54,13.9,89,22.9
2015-11-01,Folge,Afghanistan,47,,,,,431,1,0.2,148,34.3,23,5.3,97,22.5,8,1.9,0,0,58,13.5,96,22.3

价值 ("Summe") 应该总结每个国家(阿富汗)每月的价值,但也应该反映他们自己的堆栈价值(现在我正在尝试弄清楚如何使用 texture.js and custom scales 使用纹理来区分颜色,因为每个国家/地区在此图中都应该有自己的颜色,但正如我已经提到的,它们在子图层中应该不同 当我尝试将两个 csv 字段放在一个文件中时,我得到的不完全是,但类似于此 你能给我一些如何存档子层的提示(数据 structure/algorithm 或我为实现此目的所采取的措施)以便我可以继续并尝试实现纹理吗?

提前致谢

最终编辑作为对 Cyrils 的回答:

var margin = {top: 20, right: 40, bottom: 30, left: 30};
var width = document.body.clientWidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;

var x = d3.time.scale()
    .range([0, width]);

var y = d3.scale.linear()
    .range([height-10, 0]);
var dateParser = d3.time.format("%Y-%m-%d").parse;

var stack = d3.layout.stack()
    .offset("zero")
    .values(function(d) { return d.values; })
    .x(function(d) { return d.graphDate; })
    .y(function(d) { return d.value; });

var area = d3.svg.area()
    .interpolate("cardinal")
    .x(function(d) { return x(d.graphDate); })
    .y0(function(d) { return y(d.y0); })
    .y1(function(d) { return y(d.y0 + d.y); });
var z = d3.scale.category20()

doInit();
updateFirst('data/all.csv');

function doInit(){
  //make the svg and axis
  xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom")
    .ticks(d3.time.months);

  yAxis = d3.svg.axis()
      .scale(y);

  yAxisr = d3.svg.axis()
      .scale(y);
  //make svg
  var graph = d3.select(".chart").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 + ")");

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

  graph.append("g")
      .attr("class", "y axis yright")
      .attr("transform", "translate(" + width + ", 0)")
      .call(yAxis.orient("right"));

  graph.append("g")
      .attr("class", "y axis yleft")

      .call(yAxis.orient("left"));
}

function updateFirst(csvpath) {
  var nest = d3.nest()
      .key(function(d) { return d.Land+ "-" + d.Antragsart});
  //console.log(nest);

  d3.csv(csvpath, function(data) {
    data.forEach(function(d) {
      //console.log(data);
      d.graphDate = dateParser(d.Datum);
      d.value = +d.Summe;
      d.type= d.Antragsart;
    });

    var layers = stack(nest.entries(data)).sort(function(a,b){return d3.ascending(a.key, b.key)});
    console.log(layers);
    x.domain(d3.extent(data, function(d) { return d.graphDate; }));
    y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
    var k = d3.select("g .x")
      .call(xAxis);


    d3.select("g .yright")
      .call(yAxis);
    d3.select("g .yleft")
      .call(yAxis);
    d3.selectAll("defs").remove();
    d3.select(".chart svg g").selectAll("path").remove();

    d3.select(".chart svg g").selectAll("path")
        .data(layers).enter().append("path")
        //.style("fill", function(d, i) { console.log(d.key);return z(d.key); })
        .attr("class", function(d){
          var country = d.key.split("-")[0];
          var src = d.key.split("-")[1];
          return src;
        })
        .style("fill", function(d){
          var country = d.key.split("-")[0];
          var src = d.key.split("-")[1];
          if (src === "Folge"){
            var t = textures.lines().thicker(2).stroke(z(country));
            d3.select(".chart").select("svg").call(t);
            return t.url();
          } else {
            return z(country);
          }
        })
        .attr("d", function(d) { return area(d.values); });
  });
}

为了合并记录,我正在使用 d3 队列。阅读 here

这样做的目的是通过 ajax 加载 2 个 CSV,当两个都加载时调用回调。

  queue()
    .defer(d3.csv, csvpathFirst) //using queue so that callback is called after loading both CSVs
    .defer(d3.csv, csvpathSecond)
    .await(makeMyChart);

  function makeMyChart(error, first, second) {
    var data = [];

根据国家和 csv 制作嵌套函数

var nest = d3.nest() .key(函数(d) { return d.Land + "-" + d.src; //d.src 如果第一个 csv 第二个则为第一个,反之亦然 });

接下来我将像这样合并记录:

//iterate first
first.forEach(function(d) {
  d.graphDate = dateParser(d.Datum);
  d.value = +d.Summe;
  d.src = "first"
  data.push(d)
});
//iterate second
second.forEach(function(d) {
  d.graphDate = dateParser(d.Datum);
  d.value = +d.Summe;
  d.src = "second"
  data.push(d)
});
//sort layers on basis of country
var layers = stack(nest.entries(data)).sort(function(a, b) {
  return d3.ascending(a.key, b.key)
});

像这样重新生成轴:

//regenerate the axis with new domains
var k = d3.select("g .x")
  .call(xAxis);
d3.select("g .yright")
  .call(yAxis);
d3.select("g .yleft")
  .call(yAxis);

删除所有旧路径和 defs DOM 像这样:

d3.selectAll("defs").remove();
d3.select(".chart svg g").selectAll("path").remove();

接下来根据国家和第一个 csv 和第二个 csv 添加样式填充。

  .style("fill", function(d) {
    var country = d.key.split("-")[0];
    var src = d.key.split("-")[1];
    if (src === "first") {
      //use texture.js for pattern
      var t = textures.lines().thicker().stroke(z(country));
      d3.select(".chart").select("svg").call(t);
      return t.url();
    } else {
      return z(country);
    }
  })

工作代码here

希望对您有所帮助!