D3スタック棒グラフ。各スタックは、異なるグループによって異なる色に設定されています。

Nov 24 2020

問題

D3(v5)で積み上げ棒グラフを取得して、グループごとに個別に色付けされた棒を作成しようとしています(これは、図1で実行できます)。各スタックは、異なる色で表示されます(図2によって異なります)。

図3のスタックの色(つまり、グループの色のさまざまな色合いをさまざまなスタックの高さで変化させたい)の例を取得する方法が見つかりません(さまざまなグループをさまざまな色にしたい、つまり繰り返さないようにしたい場合を除く)彼らはここにいるので)。

私が提供したコード例では、2つのデータセットがあります。データを操作するのに役立つ単純なセット:

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

そして、私が実際に使おうとしているより大きなセット。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正しい色を選択するためのスケール(通常)。たとえば、最初のデータセットでは、配列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.2番目のカラースケールを作成します。

ここでのアドバイス(d3.js-積み上げ棒グラフの1つの棒に異なる色を追加する)を使用し、次のコードを追加して、異なる色のスケールを選択する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コード

3.塗りつぶしの大きなIF関数。

これが解決策である場合、私はいくつかのアドバイスをいただければ幸いです。それを機能させる、そして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ハブのコード

回答

RubenHelsloot Nov 25 2020 at 05:51

バーの長さが短い場合にバーの色を少し変えたい場合fill-opacityfill、同じものを使用して維持できます。このように、値が軽いほど、色は目立たなくなり、明るくなります。

opacity範囲を指定して新しいスケールを作成するだけです[0.3, 1]。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>


編集:グループごとにバーに色を付けたいことを知っているので、同じロジックを使用しますが、いくつかの調整を行います:

1つは、(さまざまなグループを強調するために今でも使用し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>