D3 스택 막대 그래프, 각 스택은 다른 그룹에 의해 다른 색상 설정,

Nov 24 2020

문제

나는 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

그리고 제가 실제로 사용하려는 더 큰 세트 입니다. 다음은 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. 두 번째 색상 스케일을 만듭니다.

여기에있는 조언 ( d3.js- 누적 막대 차트의 하나의 막대에 다른 색상 추가)을 사용하고 다음 코드를 추가하여 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 함수.

이것이 해결책이라면 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의 코드

답변

RubenHelsloot Nov 25 2020 at 05:51

바 길이가 더 작은 경우 바 색상을 약간 변경 하려면 동일 fill-opacity하게 사용 하고 유지할 수 있습니다 fill! 이렇게하면 값이 더 밝 으면 색상이 덜 뚜렷하고 더 밝아집니다.

opacityrange 로 새 척도 를 만드십시오 [0.3, 1]. 불투명도가 0이면 막대가 보이지 않고 일반적으로 원하지 않기 때문에 0.3을 선택했습니다. 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>