codeKracken
codeKracken

Reputation: 127

D3.js use unordered list to rotate pie chart

I have a pie chart that is created using d3.js. I can click on any slice of the pie and it works as expected. However I have tried adding an unordered list and trying to make it behave the same way as the slice. So if you click on a list item the pie should rotate the same as if you click on a slice.

In order to make this happen I need to get d.startAngle and d.endAngle. This works perfectly when clicking the slice

one of the problems that I am having is that when I click on the list item I get an error code "Uncaught ReferenceError: d is not defined"

Here is the code I am using

    // 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]);
        
    }});



The problems start around line: 244 in the rotatePie method.

I have tried binding the event from the pie to the list but that did not seem to work very well so I just added click events to li's. I figured I could count the pie slices and the li and line them up so li[0] would equal path[0] this way when li[1] is clicked path[1] would rotate to its correct position.

Here is the 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>

I am pretty much out of ideas I have been working on this for about a week. Any help or suggestions on what direction I should look into would be greatly appreciated. Thanks.

Upvotes: 1

Views: 87

Answers (2)

Umesh Maharshi
Umesh Maharshi

Reputation: 1591

I am not sure how different my approach is to the answer that is already posted, anyway I am posting this.

I believe there are multiple ways to solve this.

  1. Use your data to create both pie chart and the list and create a common onClick for both
  2. Loop through your data and create and store your onClick listeners in an array. Use this array to create onClick for your list items.

I am attaching a very crude version below based on your code.

//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>

Upvotes: 1

Andrew Reid
Andrew Reid

Reputation: 38151

While there are a number of ways that your code could likely be streamlined, I'll focus on a solution that minimizes changes.

The pie chart and the list share the same data: one pie slice and one list entry per item in the data array. You enter the pie slices based on the data, but the list entries are hard coded. Let's change this. Let's use D3 to create both, this way if you change the data, no changes to the page are required.

I'm going to store the pie data in a new variable (pieData) and use this for both the pie chart and the list - this way we can compare datums to see which list entry corresponds to which pie item:

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

Now we have a list, let's make it so when you click a list item the pie chart will act as though you clicked the slice:

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

When you click a list item, the even handler filters the wedges to find which one corresponds to the list item, it then dispatches a click event on the appropriate slice, rotating it as desired.

I have attempted to maintain your code as close as possible to the above, with some minimal changes to allow a quick demonstration of the above:

// 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>

Upvotes: 1

Related Questions