开发者问题收集

D3.js 使用无序列表旋转饼图

2021-02-27
82

我有一个使用 d3.js 创建的饼图。我可以单击饼图的任意一个切片,它都能按预期工作。但是,我尝试添加无序列表并尝试使其行为与切片相同。因此,如果您单击列表项,饼图应该像单击切片一样旋转。

为了实现这一点,我需要获取 d.startAngle 和 d.endAngle。这在单击切片时完美运行

我遇到的问题之一是,当我单击列表项时,我收到错误代码“Uncaught ReferenceError:d 未定义”

这是我正在使用的代码

    // Credit to Alan at https://codepen.io/amwill04/pen/NGmjyr
var data = [{
    "Title": "RETIREMENT",
    "Amount": 450,
    "Description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent rutrum metus vel odio convallis condimentum. Integer ullamcorper ipsum vel dui varius congue. Nulla facilisi. Morbi molestie tortor libero, ac placerat urna mollis ac. Vestibulum id ipsum mauris."
  }, {
    "Title": "CASH NEEDS",
    "Amount": 450,
    "Description": "In hac habitasse platea dictumst. Curabitur lacus neque, congue ac quam a, sagittis accumsan mauris. Suspendisse et nisl eros. Fusce nulla mi, tincidunt non faucibus vitae, aliquam vel dolor. Maecenas imperdiet, elit eget condimentum fermentum, sem lorem fringilla felis, vitae cursus lorem elit in risus."
  }, {
    "Title": "EDUCATION",
    "Amount": 450,
    "Description": "Aenean faucibus, risus sed eleifend rutrum, leo diam porttitor mauris, a eleifend ipsum ipsum ac ex. Nam scelerisque feugiat augue ac porta. Morbi massa ante, interdum sed nulla nec, finibus cursus augue. Phasellus nunc neque, blandit a nunc ut, mattis elementum arcu."
  }, {
    "Title": "INHERITANCE",
    "Amount": 600,
    "Description": "Laboriosam pariatur recusandae ipsum nisi, saepe doloremque nobis eaque omnis commodi dolor porro? Error, deserunt veritatis officiis porro libero et suscipit ad. Ipsum dolor sit amet consectetur adipisicing elit."
  }, {
    "Title": "REAL ESTATE",
    "Amount": 450,
    "Description": "Sit amet consectetur adipisicing elit. Nemo totam perspiciatis tenetur quod ipsam voluptas et consequatur labore harum obcaecati alias voluptate id sit, praesentium ratione nostrum maxime reprehenderit. Deserunt veritatis officiis porro libero et suscipit ad."
  }, {
    "Title": "BUSINESS SALES",
    "Amount": 450,
    "Description": "Consectetur adipisicing elit. Architecto illum quidem eligendi, consectetur corporis esse enim eveniet distinctio beatae dignissimos recusandae, ipsam aspernatur labore cupiditate, suscipit corrupti accusantium voluptates laborum."
  }, {
    "Title": "RESTRICTED SECURITIES",
    "Amount": 450,
    "Description": "Beatae, aperiam voluptas aut atque laborum dolorem fuga. Corporis aperiam, illo nobis suscipit perferendis natus doloremque id ratione modi, veritatis beatae maiores Lorem ipsum dolor sit, amet consectetur."
  }];
  
  var width = parseInt(d3.select('#pieChart').style('width'), 10);
  
  var height = width;
  
  var radius = (Math.min(width, height) - 15) / 2;
  
  var total = 0;      // used to calculate %s
  data.forEach((d) => {
    total += d.Amount;
  })
  
  var title = function getObject(obj) {
    titles = [];
    for (var i = 0; i < obj.length; i++) {
      titles.push(obj[i].Title);
    }
    return titles
  };
  
  // grabs the responsive value in 'counter-reset' css value
  var innerRadius = $('#pieChart').css('counter-reset').split(' ')[1];
  var arcOver = d3.arc()
    .outerRadius(radius + 10)
    .innerRadius(innerRadius);
  
  var color = d3.scaleOrdinal(); 
  color.domain(title(data))
    .range(["#215c8f", "#4072a0", "#507da8", "#6d93b7", "#96b5ce", "#b3cbdf", "#c0d6e7"]);
  
  var arc = d3.arc()
    .outerRadius(radius - 10)
  //Inner Radius is set in CSS at various breakpoints labeled .counter-reset
    .innerRadius(innerRadius);
  
  var pie = d3.pie()
    .sort(null)
    .value(function(d) {
      return +d.Amount;
    });
  
  // direction of the slice angle (for responsiveness)
  let sliceDirection = 90;
  if(window.matchMedia("(max-width: 767px)").matches) {
    sliceDirection = 180;
  }
  
  var prevSegment = null;
  var change = function(d, i) {
    //console.log(d);
  var angle = sliceDirection - ((d.startAngle * (180 / Math.PI)) +((d.endAngle - d.startAngle) * (180 / Math.PI) / 2));
  
    svg.transition()
      .duration(1000)
      .attr("transform", "translate(" + radius +
            "," + height / 2 + ") rotate(" + angle + ")");
    d3.select(prevSegment)
      .transition()
      .attr("d", arc)
      .style("transform", "translate(0px, 0px)")
      .style('filter', '');
    prevSegment = i;
  
    d3.select(i)
      .transition()
      .duration(1000)
      .attr("d", arcOver)
      .style("filter", "url(#drop-shadow)");
  };
  
  
  var svg = d3.select("#pieChart").append("svg")
    .attr("width", '100%')
    .attr("height", '100%')
    .attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height))
    .attr('preserveAspectRatio', 'xMinYMin')
    .append("g")
    .attr("transform", "translate(" + radius + "," + height / 2 + ")")
    .style("filter", "url(#drop-shadow)");
  
  
  // Create Drop Shadow on Pie Chart
  var defs = svg.append("defs");
  var filter = defs.append("filter")
      .attr("id", "drop-shadow")
      .attr("height", "130%");
  
  filter.append("feGaussianBlur")
      .attr("in", "SourceAlpha")
      .attr("stdDeviation", 5.5)
      .attr("result", "blur");
  
  filter.append("feOffset")
      .attr("in", "blur")
      .attr("dx", 0)
      .attr("dy", 0)
      .attr("result", "offsetBlur");
  
  var feMerge = filter.append("feMerge");
  feMerge.append("feMergeNode")
      .attr("in", "offsetBlur")
  feMerge.append("feMergeNode")
      .attr("in", "SourceGraphic");
  
  
  // toggle to allow animation to halfway finish before switching segment again
  var buttonToggle = true;
  var switchToggle = () => {
    setTimeout(() => {
      buttonToggle = true;
    }, 1500)
  }
  
  var timeline = new TimelineLite();
  
  var g = svg.selectAll("path")
    .data(pie(data))
    .enter().append("path")
    .style("fill", function(d) {
       
      return color(d.data.Title);
    })
  
    .attr("d", arc)
    .style("fill", function(d) {
      return color(d.data.Title);
    })
  
  
   .on("click", function(d) {
      if(buttonToggle) {
        buttonToggle = false;
        switchToggle();
        console.log(d.endAngle)
        change(d, this);
        var timeline = new TimelineLite();
  
  
        timeline.to('.content-wrapper', .5, {
          rotationX: '90deg',
          opacity: 0,
          ease: Linear.easeNone,
          onComplete: () => {$('.content-wrapper').hide();}
        }).to('.panel', .5, {
          width: '0%',
          opacity: .05,
          ease: Linear.easeNone,
          onComplete: () => {
            $('#segmentTitle').replaceWith(`<h1 id="segmentTitle">${d.data.Title} - ${Math.round((d.data.Amount/total) * 1000) / 10}%</h1>`);
            $('#segmentText').replaceWith('<p id="segmentText">' + d.data.Description + '</p>');
            $('.panel').css('background-color', `${ColorLuminance(color(d.data.Title), -0.3)}`)
          }
        });
  
  
        timeline.to('.panel', .5, {
          width: '100%',
          opacity: 1,
          ease: Linear.easeNone,
          onComplete: () => {$('.content-wrapper').show();}
        }).to('.content-wrapper', .5, {
          rotationX: '0deg',
          opacity: 1,
          ease: Linear.easeNone,
        })
      }
    });
  
  timeline.from('#pieChart', .5, {
    rotation: '-120deg',
    scale: .1,
    opacity: 0,
    ease: Power3.easeOut,
  }).from('.panel', .75, {
    width: '0%',
    opacity: 0,
    ease: Linear.easeNone,
    onComplete: () => {$('.content-wrapper').show();}
  }, '+=.55').from('.content-wrapper', .75, {
    rotationX: '-90deg',
    opacity: 0,
    ease: Linear.easeNone,
  })
  
  // Function to darken Hex colors
  function ColorLuminance(hex, lum) {
  
      // validate hex string
      hex = String(hex).replace(/[^0-9a-f]/gi, '');
      if (hex.length < 6) {
          hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
      }
      lum = lum || 0;
  
      // convert to decimal and change luminosity
      var rgb = "#", c, i;
      for (i = 0; i < 3; i++) {
          c = parseInt(hex.substr(i*2,2), 16);
          c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
          rgb += ("00"+c).substr(c.length);
      }
  
      return rgb;
  }

///////////////////////////////////////////////////////////
//Problems are starting//
var path = document.querySelectorAll("path");
//console.log(path.d.startAngle);

  document.getElementById("legend").addEventListener("click",function(e) {
    //console.log(d);
var rotatePie = function(d) {

    
    
  var angle = sliceDirection - ((d.startAngle * (180 / Math.PI)) +((d.endAngle - d.startAngle) * (180 / Math.PI) / 2));
  
    svg.transition()
      .duration(1000)
      .attr("transform", "translate(" + radius +
            "," + height / 2 + ") rotate(" + angle + ")");
    d3.select(prevSegment)
      .transition()
      .attr("d", arc)
      .style("transform", "translate(0px, 0px)")
      .style('filter', '');
    prevSegment = i;
  
    d3.select(i)
      .transition()
      .duration(1000)
      .attr("d", arcOver)
      .style("filter", "url(#drop-shadow)");
  };


    for (i = 0; i < path.length; i++) {
      //console.log(path[i]);
    }
    
    data = (typeof data == "string") ? JSON.parse(data) : data;
    if(e.target && e.target.nodeName == "LI") {
        console.log(e.target.getAttributeNode('value').value);
        let selected = e.target.getAttributeNode('value').value;
        
        //console.log(path[selected]);
        
        
        rotatePie(d, path[selected]);
        
    }});



问题始于 rotatePie 方法中的第 244 行。

我尝试将事件从饼图绑定到列表,但似乎效果不佳,所以我只是将单击事件添加到 li。我认为我可以计算饼图切片和 li 的数量,然后将它们排成一行,这样 li[0] 就等于 path[0],这样当单击 li[1] 时,path[1] 就会旋转到正确的位置。

这是 HTML

<body>
    <div class="pie-container">
    <div class="row">
      <div class="col-md-5" id="pieChart"><hr id="dotted-line"></div>
      <div id="pieText" class="col-md-7 text-container">
        <div class="panel">
          <div class="content-wrapper">
            <h1 id="segmentTitle">Select Fragment</h1>
            <p id="segmentText">Detailed information about internal systems and business operations.</p>
          </div>
        </div>
      </div>
    </div><!--.row-->
    <div class="row">
      <div class="col-md-5">
        <ul id="legend">
          <li id="retirement" value="0">RETIREMENT</li>
          <li id="cash-needs" value="1">CASH NEEDS</li>
          <li id="education" value="2">EDUCATION</li>
          <li id="inheritance" value="3">INHERITANCE</li>
          <li id="real-estate" value="4">REAL ESTATE</li>
          <li id="business-sales" value="5">BUSINESS SALES</li>
          <li id="restricted-securities" value="6">RESTRICTED SECURITIES</li>
        </ul>
      </div>
    </div>
  </div>
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.4/TweenMax.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.4/TimelineLite.min.js"></script>
  <script src="change-script.js"></script>
  </body>

我已经想不出主意了,我已经研究这个问题大约一个星期了。任何关于我应该研究什么方向的帮助或建议都将不胜感激。谢谢。

2个回答

虽然有很多方法可以简化您的代码,但我将重点介绍一种可最大程度减少更改的解决方案。

饼图和列表共享相同的数据:数据数组中每个项目一个饼图切片和一个列表条目。您可以根据数据输入饼图切片,但列表条目是硬编码的。让我们改变这一点。让我们使用 D3 来创建两者,这样,如果您更改数据,则无需更改页面。

我将把饼图数据存储在一个新变量 ( pieData ) 中,并将其用于饼图和列表 - 这样我们就可以比较数据以查看哪个列表条目对应于哪个饼图项:

d3.select("#list")
  .append("ul")
  .selectAll("li")
  .data(pieData)
  .enter()
  .append("li")
  .text(function(d) { return d.data.Title; })

现在我们有了一个列表,让我们这样做,当您单击列表项时,饼图将表现得像您单击了切片一样:

 ... // continued from above
  .on("click", function(d) {
      g.filter(function(wedge) {
         return wedge == d;    
    })
    .dispatch("click");
  })
  

当您单击列表项时,事件处理程序会过滤楔形以找出哪个楔形对应于列表项,然后在相应的切片上分派单击事件,根据需要旋转它。

我试图让您的代码尽可能接近上面的代码,并进行一些最小的更改以快速演示以上:

// Credit to Alan at https://codepen.io/amwill04/pen/NGmjyr
var data = [{
    "Title": "RETIREMENT",
    "Amount": 450,
    "Description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent rutrum metus vel odio convallis condimentum. Integer ullamcorper ipsum vel dui varius congue. Nulla facilisi. Morbi molestie tortor libero, ac placerat urna mollis ac. Vestibulum id ipsum mauris."
  }, {
    "Title": "CASH NEEDS",
    "Amount": 450,
    "Description": "In hac habitasse platea dictumst. Curabitur lacus neque, congue ac quam a, sagittis accumsan mauris. Suspendisse et nisl eros. Fusce nulla mi, tincidunt non faucibus vitae, aliquam vel dolor. Maecenas imperdiet, elit eget condimentum fermentum, sem lorem fringilla felis, vitae cursus lorem elit in risus."
  }, {
    "Title": "EDUCATION",
    "Amount": 450,
    "Description": "Aenean faucibus, risus sed eleifend rutrum, leo diam porttitor mauris, a eleifend ipsum ipsum ac ex. Nam scelerisque feugiat augue ac porta. Morbi massa ante, interdum sed nulla nec, finibus cursus augue. Phasellus nunc neque, blandit a nunc ut, mattis elementum arcu."
  }, {
    "Title": "INHERITANCE",
    "Amount": 600,
    "Description": "Laboriosam pariatur recusandae ipsum nisi, saepe doloremque nobis eaque omnis commodi dolor porro? Error, deserunt veritatis officiis porro libero et suscipit ad. Ipsum dolor sit amet consectetur adipisicing elit."
  }, {
    "Title": "REAL ESTATE",
    "Amount": 1450,
    "Description": "Sit amet consectetur adipisicing elit. Nemo totam perspiciatis tenetur quod ipsam voluptas et consequatur labore harum obcaecati alias voluptate id sit, praesentium ratione nostrum maxime reprehenderit. Deserunt veritatis officiis porro libero et suscipit ad."
  }, {
    "Title": "BUSINESS SALES",
    "Amount": 450,
    "Description": "Consectetur adipisicing elit. Architecto illum quidem eligendi, consectetur corporis esse enim eveniet distinctio beatae dignissimos recusandae, ipsam aspernatur labore cupiditate, suscipit corrupti accusantium voluptates laborum."
  }, {
    "Title": "RESTRICTED SECURITIES",
    "Amount": 450,
    "Description": "Beatae, aperiam voluptas aut atque laborum dolorem fuga. Corporis aperiam, illo nobis suscipit perferendis natus doloremque id ratione modi, veritatis beatae maiores Lorem ipsum dolor sit, amet consectetur."
  }];
  
  var width = 300
  
  var height = width;
  
  var radius = (Math.min(width, height) - 15) / 2;
  let sliceDirection = 90;
  
  var total = 0;      // used to calculate %s
  data.forEach((d) => {
    total += d.Amount;
  })
  
  var title = function getObject(obj) {
    titles = [];
    for (var i = 0; i < obj.length; i++) {
      titles.push(obj[i].Title);
    }
    return titles
  };

  var innerRadius = 10;
  var arcOver = d3.arc()
    .outerRadius(radius + 10)
    .innerRadius(innerRadius);
  
  var color = d3.scaleOrdinal(); 
  color.domain(title(data))
    .range(["#215c8f", "#4072a0", "#507da8", "#6d93b7", "#96b5ce", "#b3cbdf", "#c0d6e7"]);
  
  var arc = d3.arc()
    .outerRadius(radius - 10)
    .innerRadius(innerRadius);
  
  var pie = d3.pie()
    .sort(null)
    .value(function(d) {
      return +d.Amount;
    });
  
  var prevSegment = null;
  var change = function(d, i) {
    //console.log(d);
  var angle = sliceDirection - ((d.startAngle * (180 / Math.PI)) +((d.endAngle - d.startAngle) * (180 / Math.PI) / 2));
  
    svg.transition()
      .duration(1000)
      .attr("transform", "translate(" + radius +
            "," + height / 2 + ") rotate(" + angle + ")");
    d3.select(prevSegment)
      .transition()
      .attr("d", arc)
      .style("transform", "translate(0px, 0px)")
      .style('filter', '');
    prevSegment = i;
  
    d3.select(i)
      .transition()
      .duration(1000)
      .attr("d", arcOver)
      .style("filter", "url(#drop-shadow)");
  };
  
  
  var svg = d3.select("#pieChart").append("svg")
    .attr("width", '100%')
    .attr("height", '100%')
    .attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height))
    .attr('preserveAspectRatio', 'xMinYMin')
    .append("g")
    .attr("transform", "translate(" + radius + "," + height / 2 + ")")
    .style("filter", "url(#drop-shadow)");
  
  
  // Create Drop Shadow on Pie Chart
  var defs = svg.append("defs");
  var filter = defs.append("filter")
      .attr("id", "drop-shadow")
      .attr("height", "130%");
  
  filter.append("feGaussianBlur")
      .attr("in", "SourceAlpha")
      .attr("stdDeviation", 5.5)
      .attr("result", "blur");
  
  filter.append("feOffset")
      .attr("in", "blur")
      .attr("dx", 0)
      .attr("dy", 0)
      .attr("result", "offsetBlur");
  
  var feMerge = filter.append("feMerge");
  feMerge.append("feMergeNode")
      .attr("in", "offsetBlur")
  feMerge.append("feMergeNode")
      .attr("in", "SourceGraphic");
  
  
  // toggle to allow animation to halfway finish before switching segment again
  var buttonToggle = true;
  var switchToggle = () => {
    setTimeout(() => {
      buttonToggle = true;
    }, 1500)
  }
  
  var timeline = new TimelineLite();
  
  // get pie data:
  var pieData = pie(data);
  
  var g = svg.selectAll("path")
    .data(pieData)
    .enter().append("path")
    .style("fill", function(d) {
       
      return color(d.data.Title);
    })
  
    .attr("d", arc)
    .style("fill", function(d) {
      return color(d.data.Title);
    })
  
  
   .on("click", function(d) {
   
      if(buttonToggle) {
        buttonToggle = false;
        switchToggle();
        console.log(d.endAngle)
        change(d, this);
        var timeline = new TimelineLite();
  
  
        timeline.to('.content-wrapper', .5, {
          rotationX: '90deg',
          opacity: 0,
          ease: Linear.easeNone,
          onComplete: () => {$('.content-wrapper').hide();}
        }).to('.panel', .5, {
          width: '0%',
          opacity: .05,
          ease: Linear.easeNone,
          onComplete: () => {
            $('#segmentTitle').replaceWith(`<h1 id="segmentTitle">${d.data.Title} - ${Math.round((d.data.Amount/total) * 1000) / 10}%</h1>`);
            $('#segmentText').replaceWith('<p id="segmentText">' + d.data.Description + '</p>');
            $('.panel').css('background-color', `${ColorLuminance(color(d.data.Title), -0.3)}`)
          }
        });
  
  
        timeline.to('.panel', .5, {
          width: '100%',
          opacity: 1,
          ease: Linear.easeNone,
          onComplete: () => {$('.content-wrapper').show();}
        }).to('.content-wrapper', .5, {
          rotationX: '0deg',
          opacity: 1,
          ease: Linear.easeNone,
        })
      }
    });
  
  timeline.from('#pieChart', .5, {
    rotation: '-120deg',
    scale: .1,
    opacity: 0,
    ease: Power3.easeOut,
  }).from('.panel', .75, {
    width: '0%',
    opacity: 0,
    ease: Linear.easeNone,
    onComplete: () => {$('.content-wrapper').show();}
  }, '+=.55').from('.content-wrapper', .75, {
    rotationX: '-90deg',
    opacity: 0,
    ease: Linear.easeNone,
  })
  
  // Function to darken Hex colors
  function ColorLuminance(hex, lum) {
  
      // validate hex string
      hex = String(hex).replace(/[^0-9a-f]/gi, '');
      if (hex.length < 6) {
          hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
      }
      lum = lum || 0;
  
      // convert to decimal and change luminosity
      var rgb = "#", c, i;
      for (i = 0; i < 3; i++) {
          c = parseInt(hex.substr(i*2,2), 16);
          c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
          rgb += ("00"+c).substr(c.length);
      }
  
      return rgb;
  }
  
////
d3.select("#list")
  .append("ul")
  .selectAll("li")
  .data(pieData)
  .enter()
  .append("li")
  .text(function(d) { return d.data.Title; })
  .on("click", function(d) {
    g.filter(function(wedge) {
      return wedge == d;    
    })
    .dispatch("click");
  })
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.4/TweenMax.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.4/TimelineLite.min.js"></script>
<div id="list"></div>
<div id="pieChart"></div>
Andrew Reid
2021-02-27

我不确定我的方法与已经发布的答案有何不同,无论如何我都会发布此答案。

我相信有多种方法可以解决这个问题。

  1. 使用您的数据创建饼图和列表,并为两者创建一个通用的 onClick
  2. 循环遍历您的数据并在数组中创建和存储您的 onClick 侦听器。使用此数组为您的列表项创建 onClick。

我根据您的代码在下面附上了一个非常粗略的版本。

//create and store onclick listeners here
.each((d) => {
    const onClickHandler = () => {
      if (buttonToggle) {
        buttonToggle = false;
        switchToggle();
        console.log(d.endAngle);
        change(d, this);
        var timeline = new TimelineLite();

        timeline
          .to(".content-wrapper", 0.5, {
            rotationX: "90deg",
            opacity: 0,
            ease: Linear.easeNone,
            onComplete: () => {
              $(".content-wrapper").hide();
            },
          })
          .to(".panel", 0.5, {
            width: "0%",
            opacity: 0.05,
            ease: Linear.easeNone,
            onComplete: () => {
              $("#segmentTitle").replaceWith(
                `<h1 id="segmentTitle">${d.data.Title} - ${
                  Math.round((d.data.Amount / total) * 1000) / 10
                }%</h1>`
              );
              $("#segmentText").replaceWith(
                '<p id="segmentText">' + d.data.Description + "</p>"
              );
              $(".panel").css(
                "background-color",
                `${ColorLuminance(color(d.data.Title), -0.3)}`
              );
            },
          });

        timeline
          .to(".panel", 0.5, {
            width: "100%",
            opacity: 1,
            ease: Linear.easeNone,
            onComplete: () => {
              $(".content-wrapper").show();
            },
          })
          .to(".content-wrapper", 0.5, {
            rotationX: "0deg",
            opacity: 1,
            ease: Linear.easeNone,
          });
      }
    };
    onClickListenersArray.push(onClickHandler);
  })
  .on("click", function (d, i) {
    return onClickListenersArray[i];
  });

// onClick listeners for your list
const legendList = d3.selectAll("li");

legendList.on("click", (d, i) => {
  onClickListenersArray[i]();
});
<body>
  <div class="pie-container">
    <div class="row">
      <div class="col-md-5" id="pieChart"><hr id="dotted-line" /></div>
      <div id="pieText" class="col-md-7 text-container">
        <div class="panel">
          <div class="content-wrapper">
            <h1 id="segmentTitle">Select Fragment</h1>
            <p id="segmentText">
              Detailed information about internal systems and business
              operations.
            </p>
          </div>
        </div>
      </div>
    </div>
    <!--.row-->
    <div class="row">
      <div class="col-md-5">
        <ul id="legend">
          <li id="retirement" value="0">RETIREMENT</li>
          <li id="cash-needs" value="1">CASH NEEDS</li>
          <li id="education" value="2">EDUCATION</li>
          <li id="inheritance" value="3">INHERITANCE</li>
          <li id="real-estate" value="4">REAL ESTATE</li>
          <li id="business-sales" value="5">BUSINESS SALES</li>
          <li id="restricted-securities" value="6">RESTRICTED SECURITIES</li>
        </ul>
      </div>
    </div>
  </div>
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.4/TweenMax.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.4/TimelineLite.min.js"></script>
  <script>
    var data = [
      {
        Title: "RETIREMENT",
        Amount: 450,
        Description:
          "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent rutrum metus vel odio convallis condimentum. Integer ullamcorper ipsum vel dui varius congue. Nulla facilisi. Morbi molestie tortor libero, ac placerat urna mollis ac. Vestibulum id ipsum mauris.",
      },
      {
        Title: "CASH NEEDS",
        Amount: 450,
        Description:
          "In hac habitasse platea dictumst. Curabitur lacus neque, congue ac quam a, sagittis accumsan mauris. Suspendisse et nisl eros. Fusce nulla mi, tincidunt non faucibus vitae, aliquam vel dolor. Maecenas imperdiet, elit eget condimentum fermentum, sem lorem fringilla felis, vitae cursus lorem elit in risus.",
      },
      {
        Title: "EDUCATION",
        Amount: 450,
        Description:
          "Aenean faucibus, risus sed eleifend rutrum, leo diam porttitor mauris, a eleifend ipsum ipsum ac ex. Nam scelerisque feugiat augue ac porta. Morbi massa ante, interdum sed nulla nec, finibus cursus augue. Phasellus nunc neque, blandit a nunc ut, mattis elementum arcu.",
      },
      {
        Title: "INHERITANCE",
        Amount: 600,
        Description:
          "Laboriosam pariatur recusandae ipsum nisi, saepe doloremque nobis eaque omnis commodi dolor porro? Error, deserunt veritatis officiis porro libero et suscipit ad. Ipsum dolor sit amet consectetur adipisicing elit.",
      },
      {
        Title: "REAL ESTATE",
        Amount: 450,
        Description:
          "Sit amet consectetur adipisicing elit. Nemo totam perspiciatis tenetur quod ipsam voluptas et consequatur labore harum obcaecati alias voluptate id sit, praesentium ratione nostrum maxime reprehenderit. Deserunt veritatis officiis porro libero et suscipit ad.",
      },
      {
        Title: "BUSINESS SALES",
        Amount: 450,
        Description:
          "Consectetur adipisicing elit. Architecto illum quidem eligendi, consectetur corporis esse enim eveniet distinctio beatae dignissimos recusandae, ipsam aspernatur labore cupiditate, suscipit corrupti accusantium voluptates laborum.",
      },
      {
        Title: "RESTRICTED SECURITIES",
        Amount: 450,
        Description:
          "Beatae, aperiam voluptas aut atque laborum dolorem fuga. Corporis aperiam, illo nobis suscipit perferendis natus doloremque id ratione modi, veritatis beatae maiores Lorem ipsum dolor sit, amet consectetur.",
      },
    ];

    var width = parseInt(d3.select("#pieChart").style("width"), 10);

    var height = width;

    var radius = (Math.min(width, height) - 15) / 2;

    var total = 0; // used to calculate %s
    data.forEach((d) => {
      total += d.Amount;
    });

    var title = function getObject(obj) {
      titles = [];
      for (var i = 0; i < obj.length; i++) {
        titles.push(obj[i].Title);
      }
      return titles;
    };

    // grabs the responsive value in 'counter-reset' css value
    var innerRadius = $("#pieChart").css("counter-reset").split(" ")[1];
    var arcOver = d3
      .arc()
      .outerRadius(radius + 10)
      .innerRadius(innerRadius);

    var color = d3.scaleOrdinal();
    color
      .domain(title(data))
      .range([
        "#215c8f",
        "#4072a0",
        "#507da8",
        "#6d93b7",
        "#96b5ce",
        "#b3cbdf",
        "#c0d6e7",
      ]);

    var arc = d3
      .arc()
      .outerRadius(radius - 10)
      //Inner Radius is set in CSS at various breakpoints labeled .counter-reset
      .innerRadius(innerRadius);

    var pie = d3
      .pie()
      .sort(null)
      .value(function (d) {
        return +d.Amount;
      });

    // direction of the slice angle (for responsiveness)
    let sliceDirection = 90;
    if (window.matchMedia("(max-width: 767px)").matches) {
      sliceDirection = 180;
    }

    var prevSegment = null;
    var change = function (d, i) {
      //console.log(d);
      var angle =
        sliceDirection -
        (d.startAngle * (180 / Math.PI) +
          ((d.endAngle - d.startAngle) * (180 / Math.PI)) / 2);

      svg
        .transition()
        .duration(1000)
        .attr(
          "transform",
          "translate(" + radius + "," + height / 2 + ") rotate(" + angle + ")"
        );
      d3.select(prevSegment)
        .transition()
        .attr("d", arc)
        .style("transform", "translate(0px, 0px)")
        .style("filter", "");
      prevSegment = i;

      d3.select(i)
        .transition()
        .duration(1000)
        .attr("d", arcOver)
        .style("filter", "url(#drop-shadow)");
    };

    var svg = d3
      .select("#pieChart")
      .append("svg")
      .attr("width", "100%")
      .attr("height", "100%")
      .attr(
        "viewBox",
        "0 0 " + Math.min(width, height) + " " + Math.min(width, height)
      )
      .attr("preserveAspectRatio", "xMinYMin")
      .append("g")
      .attr("transform", "translate(" + radius + "," + height / 2 + ")")
      .style("filter", "url(#drop-shadow)");

    // Create Drop Shadow on Pie Chart
    var defs = svg.append("defs");
    var filter = defs
      .append("filter")
      .attr("id", "drop-shadow")
      .attr("height", "130%");

    filter
      .append("feGaussianBlur")
      .attr("in", "SourceAlpha")
      .attr("stdDeviation", 5.5)
      .attr("result", "blur");

    filter
      .append("feOffset")
      .attr("in", "blur")
      .attr("dx", 0)
      .attr("dy", 0)
      .attr("result", "offsetBlur");

    var feMerge = filter.append("feMerge");
    feMerge.append("feMergeNode").attr("in", "offsetBlur");
    feMerge.append("feMergeNode").attr("in", "SourceGraphic");

    // toggle to allow animation to halfway finish before switching segment again
    var buttonToggle = true;
    var switchToggle = () => {
      setTimeout(() => {
        buttonToggle = true;
      }, 1500);
    };

    const onClickListenersArray = [];

    var timeline = new TimelineLite();

    var g = svg
      .selectAll("path")
      .data(pie(data))
      .enter()
      .append("path")
      .style("fill", function (d) {
        return color(d.data.Title);
      })

      .attr("d", arc)
      .style("fill", function (d) {
        return color(d.data.Title);
      })
      .each((d) => {
        const onClickHandler = () => {
          if (buttonToggle) {
            buttonToggle = false;
            switchToggle();
            console.log(d.endAngle);
            change(d, this);
            var timeline = new TimelineLite();

            timeline
              .to(".content-wrapper", 0.5, {
                rotationX: "90deg",
                opacity: 0,
                ease: Linear.easeNone,
                onComplete: () => {
                  $(".content-wrapper").hide();
                },
              })
              .to(".panel", 0.5, {
                width: "0%",
                opacity: 0.05,
                ease: Linear.easeNone,
                onComplete: () => {
                  $("#segmentTitle").replaceWith(
                    `<h1 id="segmentTitle">${d.data.Title} - ${
                      Math.round((d.data.Amount / total) * 1000) / 10
                    }%</h1>`
                  );
                  $("#segmentText").replaceWith(
                    '<p id="segmentText">' + d.data.Description + "</p>"
                  );
                  $(".panel").css(
                    "background-color",
                    `${ColorLuminance(color(d.data.Title), -0.3)}`
                  );
                },
              });

            timeline
              .to(".panel", 0.5, {
                width: "100%",
                opacity: 1,
                ease: Linear.easeNone,
                onComplete: () => {
                  $(".content-wrapper").show();
                },
              })
              .to(".content-wrapper", 0.5, {
                rotationX: "0deg",
                opacity: 1,
                ease: Linear.easeNone,
              });
          }
        };
        onClickListenersArray.push(onClickHandler);
      })
      .on("click", function (d, i) {
        return onClickListenersArray[i];
      });

    timeline
      .from("#pieChart", 0.5, {
        rotation: "-120deg",
        scale: 0.1,
        opacity: 0,
        ease: Power3.easeOut,
      })
      .from(
        ".panel",
        0.75,
        {
          width: "0%",
          opacity: 0,
          ease: Linear.easeNone,
          onComplete: () => {
            $(".content-wrapper").show();
          },
        },
        "+=.55"
      )
      .from(".content-wrapper", 0.75, {
        rotationX: "-90deg",
        opacity: 0,
        ease: Linear.easeNone,
      });

    // Function to darken Hex colors
    function ColorLuminance(hex, lum) {
      // validate hex string
      hex = String(hex).replace(/[^0-9a-f]/gi, "");
      if (hex.length < 6) {
        hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
      }
      lum = lum || 0;

      // convert to decimal and change luminosity
      var rgb = "#",
        c,
        i;
      for (i = 0; i < 3; i++) {
        c = parseInt(hex.substr(i * 2, 2), 16);
        c = Math.round(Math.min(Math.max(0, c + c * lum), 255)).toString(16);
        rgb += ("00" + c).substr(c.length);
      }

      return rgb;
    }

    const legendList = d3.selectAll("li");

    legendList.on("click", (d, i) => {
      onClickListenersArray[i]();
    });
  </script>
</body>
Umesh Maharshi
2021-02-27