D3 堆叠条形图,每个堆叠由不同的组设置不同的颜色,

D3 stacked bar graph, with each stack a different colour set by the different groups,

问题

我正在尝试在 D3 (v5) 中获得堆叠条形图,以便为不同的组设置单独的彩色条(我可以做到,图 1),每个堆叠都有不同的颜色(取决于图 2) .

我找不到获得堆栈着色的方法(即我希望组颜色的不同阴影随着不同的堆栈高度而变化)图 3 中的示例(除了我希望不同的组是不同的颜色,即不重复这里的颜色)。

在我提供的代码示例中有两组数据。一个简单的集合,帮助玩转数据:

Animal,Group,A,B,C,D
Dog,Domestic,10,10,20,5
Cat,Domestic,20,5,10,10
Mouse,Pest,75,5,35,0 
Lion,Africa,5,5,30,25
Elephant,Africa,15,15,20,20
Whale,Marine,35,20,10,45
Shark,Marine,45,55,0, 60
Fish,Marine,20, 5,30,10

a bigger set 我实际上正在尝试使用。 这是我正在尝试开发的 bl.ocks.org 代码:

// set the dimensions and margins of the graph
const margin = {
    top: 90,
    right: 20,
    bottom: 30,
    left: 40
  },
  width = 960 - margin.left - margin.right,
  height = 960 - margin.top - margin.bottom;

const svg = d3.select("#my_dataviz")
  .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 + ")");

const y = d3.scaleBand()
  .range([0, height])
  .padding(0.1);
const x = d3.scaleLinear()
  .range([0, width]);
const z = d3.scaleOrdinal()
  .range(["none", "lightsteelblue", "steelblue", "darksteelblue"]);

d3.csv("https://gist.githubusercontent.com/JimMaltby/844ca313589e488b249b86ead0d621a9/raw/f328ad6291ffd3c9767a2dbdba5ce8ade59a5dfa/TimeBarDummyFormat.csv", d3.autoType, (d, i, columns) => {
      var i = 3;
      var t = 0;
      for (; i < columns.length; ++i)
        t += d[columns[i]] = +d[columns[i]];
      d.total = t;
      return d;
    }

  ).then(function(data) {
    const keys = data.columns.slice(3); // takes the column names, ignoring the first 3 items = ["EarlyMin","EarlyAll", "LateAll", "LateMax"]

    // List of groups = species here = value of the first column called group -> I show them on the X axis
    const Groups = d3.map(data, d => d.Group);
    y.domain(data.map(d => d.Ser));
    x.domain([2000, d3.max(data, d => d.total)]).nice();
    z.domain(keys);

    svg.append("g")
      .selectAll("g")
      .data(d3.stack().keys(keys)(data))
      .enter().append("g")
      .attr("fill", d => z(d.key)) //Color is assigned here because you want everyone for the series to be the same color
      .selectAll("rect")
      .data(d => d)
      .enter()
      .append("rect")
      .attr("y", d => y(d.data.Ser))
      .attr("x", d => x(d[0]))
      .attr("height", y.bandwidth())
      .attr("width", d => x(d[1]) - x(d[0]));

    svg.append("g")
      .attr("transform", "translate(0,0)")
      .call(d3.axisTop(x));

    svg.append("g")
      .call(d3.axisLeft(y));
  });
.bar {
  fill: rgb(70, 131, 180);
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="my_dataviz"></div>

JSFiddle: https://jsfiddle.net/yq7bkvdL/

我试过的

我觉得我只是遗漏了一些简单的东西,但我是一个编码菜鸟,我的编码非常初级,所以我无法解决。

我想我要么将填充 attr 放在了错误的位置。或者是我不懂如何select d3.stack.

的nested/hierarchical数据中的key

我试过各种方法,都没有成功:

1.颜色数组 我尝试编写一个函数来创建颜色数组,方法是在“键”和“组”values/names 上迭代(使用 forEach)并将它们连接起来以创建一个我可以使用的数组使用 d3 比例(顺序)到 select 正确的颜色。例如,对于第一个数据集,它将创建一个数组 ColoursID [DomesticA, DomesticB, DomesticC, DomesticD,PestA, PestB...],然后匹配 ColourS ["grey", "lightgreen", "green", "darkgreen", "yellow", ...]

中的颜色

下面是这样做的尝试,加上注释掉的各种其他探索。

  // List of groups = species here = value of the first column called group -> I show them on the X axis
  const stack = d3.stack().keys(stackKeys)(data);

//const Groups = d3.map(data, d => d.Group);
//const ColourID = d3.map(data, d => d.Group && d.key);
//  const stackID = stack.data // //stack[3].key = "D" // [2][6].data.Group  = "Marine"
// const Test1 = Object.entries(stack).forEach(d => d.key);
        
const stackB = stack.forEach(function(d,i,j){
                                                //var a =  Object.keys(d)//= list of 3rd Level keys "0,..7,key,index"
                                                //var a =  Object.keys(d).forEach((key) => key) "undefined"
                                                //var   a = d.key //= "D" for all
                        d.forEach(function(d,i){
                                            //var a =  d.keys // = "function keys{} ([native code])"
                          //var a =  Object.keys(d)
                          //var a =  Object.keys(d) //= list of 2nd Level keys "0,1,data"
                                                    var  b = data[i]["Group"]   
                                    d.forEach(function(d){
                                                    //var a = [d]["key"] // = "undefined"
                          //var a = Object.keys(d).forEach((key) => d[key]) // = "undefined"
                              var a = Object.keys(d) //= ""
                           //   var a =  d.keys //= "undefined"
                                data[i]["colourID"] = b + " a" + "-b " + a //d.key
                                                                })  
                        })    
               });
  
  console.log(stack)
      svg.append("g")
        .selectAll("g")
        .data(stack)
        .enter().append("g")   
        //.attr("fill", d => z(d.data.Group) ) //Color is assigned here because you want everyone for the series to be the same color
        .selectAll("rect")
        .data(d => d)
        .enter().append("rect")
        .attr("fill", d => colourZ(d.data.colourID)) //Color is assigned here because you want each Group to be a different colour **how do you vary the blocks?**
        .attr("y", d => y(d.data.Animal) )      //uses the Column of data Animal to seperate bars
        .attr("x", d=> x(d[0]) )                //
        .attr("height", y.bandwidth() )         //
        .attr("width", d=> x(d[1]) - x(d[0]));  //
            

VizHub 代码:https://vizhub.com/JimMaltby/373f1dbb42ad453787dc0055dee7db81?file=index.js

2。创建第二个色标:

我使用了此处的建议 (d3.js-adding different colors to one bar in stacked bar chart),将 if 函数添加到 select 不同的色标,方法是添加以下代码:

//-----------------------------BARS------------------------------//

      // append the rectangles for the bar chart
      svg.append("g")
        .selectAll("g")
        .data(stack)
        .enter().append("g")      

            //Color is assigned here because you want everyone for the series to be the same color
        .selectAll("rect")
        .data(d => d)
        .enter().append("rect")
        .attr("fill", d => 
              d.data.Group == "Infrastructure" 
              ? z2(d.key) 
              : z(d.key))
        //.attr("class", "bar")
        .attr("y", d => y(d.data.Ser) )         //Vert2Hor **x to y** **x(d.data.Ser) to y(d.data.Ser)**
        .attr("x", d=> x(d[0]) )                //Vert2Hor **y to x** **y(d[1]) to x(d[0])**
        .attr("height", y.bandwidth() )         //Vert2Hor **"width" to "height"**  **x.bandwidth to y.bandwidth** 
        .attr("width", d=> x(d[1]) - x(d[0]));  //Vert2Hor **"height" to "width"**  **y(d[0]) - y(d[1]) to x(d[1]) - x(d[0])**

VizHub code

3。 fill.

一个很大的IF函数

如果这是解决方案,我将不胜感激 a. 使其工作,然后 b. 有更有效的方法

看来我又在努力select“堆栈”数据数组的“键”。您会注意到,我一直在尝试不同的方法来 select 此处代码中的密钥,但没有成功:(.

        .attr("fill", function(d,i, j) {        
            if (d.data.Group === "Domestic") {
                if (d.key === "A") { return "none"}
                    else if (d.key === "B") { return "lightblue"}
                    else if (d.key === "C") { return "blue"}
                else if (d.key === "D") { return "darkblue"}
                else  { return "forestgreen"}
            }
            else if (d.data.Group === "Pest") {
                if (d.key === "A") { return "yellow"}
                    else if (d.key === "B") { return "lightorange"}
                    else if (d.key === "C") { return "orange"}
                else if (d.key === "D") { return "darkorange"}
                else  { return "Brown"} //"yellow", "lightorange", "orange", ""
            }
            else if (d.data.Group === "Africa") {
                if (Object.keys(root.data) === 1) { return "grey"}
                    else if (d.key === "B") { return "lightred"}
                    else if (d.key === "C") { return "red"}
                else if (d.key === "D") { return "darkred"}
                else  { return "pink"}
            }
            else if (d.data.Group == "Marine") {
                if (stackKeys == "A") { return "lightgrey"}
                    else if (stackKeys[d] == "B") { return "lightblue"}
                    else if (stackKeys[i] == "C") { return "blue"}
                else if (stackKeys[3] == "D") { return "darkblue"}
                else  { return "steelblue"}
            }
            else { return "black" }             
                ;})

Viz Hub

中的代码

如果你想在条形长度较小的情况下稍微改变条形颜色,你可以使用 fill-opacity 并保持 fill 不变!这样,如果值较浅,颜色就不那么明显和较浅。

只需创建一个范围 [0.3, 1] 的新比例 opacity。我选择 0.3 是因为 0 不透明度意味着栏是不可见的,而你通常不希望这样。我添加了一个单独的值 d.height 来表示栏的整个可见高度,它与开始分开(但相当于 d.Min + d.All + d.Max)。现在,只需将该属性应用于每个条形即可。

您可以选择将范围设置为 [0, 1] 而不是对域使用 d3.extent - 这可能会导致类似的结果,尽管您可以通过思考发现一些差异实验。

现在每个柱上都设置了 fill-opacity 属性。因此同一堆栈中的条具有相同的 fill-opacity 值。请注意,这完全是可选的,您也可以应用不同的值。

// set the dimensions and margins of the graph
const margin = {
    top: 90,
    right: 20,
    bottom: 30,
    left: 40
  },
  width = 960 - margin.left - margin.right,
  height = 960 - margin.top - margin.bottom;

const svg = d3.select("#my_dataviz")
  .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 + ")");

const y = d3.scaleBand()
  .range([0, height])
  .padding(0.1);
const x = d3.scaleLinear()
  .range([0, width]);
const z = d3.scaleOrdinal()
  .range(["none", "lightsteelblue", "steelblue", "darksteelblue"]);
const opacity = d3.scaleLinear()
  .range([0.3, 1]);

d3.csv("https://gist.githubusercontent.com/JimMaltby/844ca313589e488b249b86ead0d621a9/raw/f328ad6291ffd3c9767a2dbdba5ce8ade59a5dfa/TimeBarDummyFormat.csv", d3.autoType, (d, i, columns) => {
      var i = 3;
      var t = 0;
      for (; i < columns.length; ++i)
        t += d[columns[i]] = +d[columns[i]];
      d.total = t;
      d.height = d.total - d.Start;
      return d;
    }

  ).then(function(data) {
    const keys = data.columns.slice(3); // takes the column names, ignoring the first 3 items = ["EarlyMin","EarlyAll", "LateAll", "LateMax"]

    // List of groups = species here = value of the first column called group -> I show them on the X axis
    const Groups = d3.map(data, d => d.Group);
    y.domain(data.map(d => d.Ser));
    x.domain([2000, d3.max(data, d => d.total)]).nice();
    z.domain(keys);
    opacity.domain(d3.extent(data, d => d.height));
    console.log(opacity.domain());

    svg.append("g")
      .selectAll("g")
      .data(d3.stack().keys(keys)(data))
      .enter().append("g")
      .attr("fill", d => z(d.key))
      .selectAll("rect")
      .data(d => d)
      .enter()
      .append("rect")
      .attr("y", d => y(d.data.Ser))
      .attr("x", d => x(d[0]))
      .attr("height", y.bandwidth())
      .attr("width", d => x(d[1]) - x(d[0]))
      .attr("fill-opacity", d => opacity(d.data.height));

    svg.append("g")
      .attr("transform", "translate(0,0)")
      .call(d3.axisTop(x));

    svg.append("g")
      .call(d3.axisLeft(y));
  });
.bar {
  fill: rgb(70, 131, 180);
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="my_dataviz"></div>


编辑:知道你想按组给条形着色,我会使用相同的逻辑,但做一些调整:

首先,我将 z 切换为处理 fill-opacity(我仍然使用它来强调不同的组),并对颜色使用新的序数标度 group。该比例的关键只是预先存在的字段 d.Group.

// set the dimensions and margins of the graph
const margin = {
    top: 90,
    right: 20,
    bottom: 30,
    left: 40
  },
  width = 960 - margin.left - margin.right,
  height = 960 - margin.top - margin.bottom;

const svg = d3.select("#my_dataviz")
  .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 + ")");

const y = d3.scaleBand()
  .range([0, height])
  .padding(0.1);
const x = d3.scaleLinear()
  .range([0, width]);
const z = d3.scaleOrdinal()
  .range([0.25, 0.5, 0.75, 1]);
const group = d3.scaleOrdinal()
  .range(["darkgreen", "darkred", "steelblue", "purple"]);

d3.csv("https://gist.githubusercontent.com/JimMaltby/844ca313589e488b249b86ead0d621a9/raw/f328ad6291ffd3c9767a2dbdba5ce8ade59a5dfa/TimeBarDummyFormat.csv", d3.autoType, (d, i, columns) => {
      var i = 3;
      var t = 0;
      for (; i < columns.length; ++i)
        t += d[columns[i]] = +d[columns[i]];
      d.total = t;
      return d;
    }

  ).then(function(data) {
    const keys = data.columns.slice(3); // takes the column names, ignoring the first 3 items = ["EarlyMin","EarlyAll", "LateAll", "LateMax"]

    y.domain(data.map(d => d.Ser));
    x.domain([2000, d3.max(data, d => d.total)]).nice();
    z.domain(keys);
    group.domain(data.map(d => d.Group));

    svg.append("g")
      .selectAll("g")
      .data(d3.stack().keys(keys)(data))
      .enter().append("g")
      .attr("fill-opacity", d => z(d.key))
      .selectAll("rect")
      .data(d => d)
      .enter()
      .append("rect")
      .attr("y", d => y(d.data.Ser))
      .attr("x", d => x(d[0]))
      .attr("height", y.bandwidth())
      .attr("width", d => x(d[1]) - x(d[0]))
      .attr("fill", d => group(d.data.Group));
      

    svg.append("g")
      .attr("transform", "translate(0,0)")
      .call(d3.axisTop(x));

    svg.append("g")
      .call(d3.axisLeft(y));
  });
.bar {
  fill: rgb(70, 131, 180);
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="my_dataviz"></div>


编辑 2:如果您想自己指定颜色,我会使用颜色键映射:

// set the dimensions and margins of the graph
const margin = {
    top: 90,
    right: 20,
    bottom: 30,
    left: 40
  },
  width = 960 - margin.left - margin.right,
  height = 960 - margin.top - margin.bottom;

const svg = d3.select("#my_dataviz")
  .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 + ")");

const y = d3.scaleBand()
  .range([0, height])
  .padding(0.1);
const x = d3.scaleLinear()
  .range([0, width]);
const z = d3.scaleOrdinal()
  .range([0.25, 0.5, 0.75, 1]);
const group = d3.scaleOrdinal()
  .range([
    { Start: "none", Min: "lightgreen", All: "green", Max: "darkgreen" },
    { Start: "none", Min: "indianred", All: "red", Max: "darkred" },
    { Start: "none", Min: "lightsteelblue", All: "steelblue", Max: "darksteelblue" }
  ]);

d3.csv("https://gist.githubusercontent.com/JimMaltby/844ca313589e488b249b86ead0d621a9/raw/f328ad6291ffd3c9767a2dbdba5ce8ade59a5dfa/TimeBarDummyFormat.csv", d3.autoType, (d, i, columns) => {
      var i = 3;
      var t = 0;
      for (; i < columns.length; ++i)
        t += d[columns[i]] = +d[columns[i]];
      d.total = t;
      return d;
    }

  ).then(function(data) {
    const keys = data.columns.slice(3); // takes the column names, ignoring the first 3 items = ["EarlyMin","EarlyAll", "LateAll", "LateMax"]

    y.domain(data.map(d => d.Ser));
    x.domain([2000, d3.max(data, d => d.total)]).nice();
    z.domain(keys);
    group.domain(data.map(d => d.Group));

    svg.append("g")
      .selectAll("g")
      .data(d3.stack().keys(keys)(data))
      .enter().append("g")
      .each(function(e) {
        d3.select(this)
          .selectAll("rect")
          .data(d => d)
          .enter()
          .append("rect")
          .attr("y", d => y(d.data.Ser))
          .attr("x", d => x(d[0]))
          .attr("height", y.bandwidth())
          .attr("width", d => x(d[1]) - x(d[0]))
          .attr("fill", d => group(d.data.Group)[e.key]);
      });
      

    svg.append("g")
      .attr("transform", "translate(0,0)")
      .call(d3.axisTop(x));

    svg.append("g")
      .call(d3.axisLeft(y));
  });
.bar {
  fill: rgb(70, 131, 180);
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="my_dataviz"></div>