【问题标题】:D3 stacked bar graph, with each stack a different colour set by the different groups,D3堆叠条形图,每个堆叠由不同组设置不同的颜色,
【发布时间】:2021-03-06 13:32:25
【问题描述】:

问题

我正在尝试在 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 放在错误的位置。或者是我不明白如何在d3.stack的嵌套/分层数据中选择键。

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

1.多种颜色 我已经尝试编写一个函数来创建一个颜色数组,通过迭代(使用forEach)“键”和“组”值/名称并将它们连接起来创建一个可以与 d3 Scale 一起使用的数组(序数)选择正确的颜色。例如,对于第一个数据集,它将创建一个数组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 函数来选择不同的色标,方法是添加以下代码:

//-----------------------------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.一个大的 IF 函数填充。

如果这是解决方案,我将不胜感激 a. 让它发挥作用,然后 b. 采用更有效的方式来实现它

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

        .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中的代码

【问题讨论】:

  • 所以您试图根据堆栈是 a、b、c 还是 d 来填充?
  • 其实你有可能开始一个jsfiddle吗?
  • 虽然您在制定和研究问题方面付出了多少努力,但我不清楚该去哪里寻找。在您分享的 4 个代码示例中,我假设 bl.ocks.org 是开始工作的基础。但是,仅供参考 - 虽然您的 cmets 可能对您有意义,但它们会使您的代码更加复杂,外人更难理解。请确认我内联的代码(来自 bl.ocks.org 示例,我删除了一些非必要的东西)是您开始的基础?
  • @RubenHelsloot 你内联的代码是我开始的基础。感谢您的坚持,尽管我发表了评论。
  • 而您正在寻找的是让所有片段都具有不同的颜色?那么一张由 4 叠 3 条组成的图表,每条不是 3 种颜色,而是 12 种颜色?

标签: javascript d3.js colors stack key


【解决方案1】:

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

只需创建一个范围为[0.3, 1] 的新比例opacity。我选择 0.3 是因为 0 不透明度意味着该条是不可见的,而您通常不希望这样。我添加了一个单独的值 d.height 来表示栏的整个可见高度,它与 start 是分开的(但等效于 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>

【讨论】:

  • 抱歉,我的解释一定不是很清楚。这不是我想要做的。
  • 感谢您的尝试。我在上面的 cmets 中对此进行了澄清。如果我理解正确,这会通过每个堆栈的“d.data.height”来改变每个条的不透明度。
  • 我添加了一个新答案,使用组
  • 酷,这更接近我所追求的,但理想情况下我希望对颜色有更多的控制,即我可以为每个组中的单个堆栈选择特定的颜色。这可能吗?
  • 感觉我需要一些方法来使用某种 IF 函数来设置 .append(“g”) 和 .append(“rect”) 中的 .fill。但我猜有一种更简单的方法可以做到这一点。
猜你喜欢
  • 2021-02-25
  • 1970-01-01
  • 2014-05-11
  • 2022-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-09
相关资源
最近更新 更多