圓餅圖 Pie Chart

每月財務分配

程式碼

          
          //html 
          <div class="expensesChart"></div>

          //js
          // 建立svg
          const currentWidth = parseInt(
              d3.select(".expensesChart").style("width")
            ),
            height = 400,
            margin = 40;
  
          const svg = d3
            .select(".expensesChart")
            .append("svg")
            .attr("width", currentWidth)
            .attr("height", height);

          const data = [
            { item: "交通", data: 3000 },
            { item: "房租", data: 12000 },
            { item: "日常用品", data: 1400 },
            { item: "吃飯", data: 4000 },
            { item: "交際應酬", data: 2400 },
          ];
  
          // 建立圓餅集合標籤g
          svg
            .append("g")
            .attr("class", "slices")
            .attr("transform", 
              `translate(${currentWidth / 2}, ${height / 2})`
            );
  
          // 設定顏色
          const color = d3.scaleOrdinal().range(d3.schemeSet2);
  
          // 設定圓餅半徑
          const radius = Math.min(currentWidth, height) / 2 - margin;
  
          // 設定圓餅建構函式
          const piechartGenerator = d3.pie().value((d) => d.data);
  
          // 用innerRadius 跟 outerRadius 設定圓餅內圈外圈的半徑
          const arc = d3
              .arc()
              .innerRadius(0)
              .outerRadius(radius)
              .padAngle(0);
              
          const outerArc = d3
              .arc()
              .outerRadius(radius * 0.9)
              .innerRadius(radius * 0.9);
  
          // 圓餅圖建構函式帶入資料
          const pieChartData = piechartGenerator(data);
  
          // 建立pie
          const expensesChart = svg
            .select(".slices")
            .selectAll("path")
            .data(pieChartData)
            .enter();
  
          // 綁定圓弧路徑
          expensesChart
            .append("path")
            .attr("d", arc)
            .attr("class", "arc")
            .attr("fill", color)
            .attr("stroke", "#fff")
            .style("stroke-width", "3px")
            .style("opacity", 1);
  
          // 加上每個區塊的數字標示
          // 計算每塊資料的百分占比,先用 d3.sum 加總全部資料,再將資料一一除上總數
          const total = d3.sum(data, (d) => d.data);
          data.forEach((i) => {
            i.percentage = Math.round((i.data / total) * 100);
          });
  
          // 調整數字標示的位置
          const textArc = d3
            .arc()
            .innerRadius(radius)
            .outerRadius(radius - 10);
  
          // 綁定數字標籤位置
          expensesChart
            .append("text")
            .attr("transform", (d) => `translate(${textArc.centroid(d)})`)
            .text((d) => d.data.item + d.data.percentage + "%")
            .style("text-anchor", "middle")
            .style("font-size", 16)
            .style("fill", "black");
          
        
每月財務分配-滑鼠互動

程式碼

          
            const drawExpensesChartHover = ()=>{
              const data = [
                { item: "交通", data: 3000 },
                { item: "房租", data: 12000 },
                { item: "日常用品", data: 1400 },
                { item: "吃飯", data: 4000 },
                { item: "交際應酬", data: 2400 },
              ];
      
              // svg
              const currentWidth = parseInt(
                  d3.select(".expensesChartHover").style("width")
                ),
                height = 400,
                margin = 40;
      
              // 先設定 svg 大小
              const svg = d3
                .select(".expensesChartHover")
                .append("svg")
                .attr("width", currentWidth)
                .attr("height", height);
      
              // 圓餅集合標籤
              svg
                .append("g")
                .attr("class", "slices")
                .attr("transform", `translate(${currentWidth / 2}, ${height / 2})`);
      
              // 設定顏色
              const color = d3.scaleOrdinal().range(d3.schemeSet2);
      
              // 設定圓餅半徑
              const radius = Math.min(currentWidth, height) / 2 - margin;
      
              // 將資料綁定到圓餅圖上:
              const piechartGenerator = d3.pie().value((d) => d.data);
      
              // innerRadius 跟 outerRadius 設定圓餅內圈外圈的大小 radius
              const arc = d3.arc().innerRadius(0).outerRadius(radius).padAngle(0), // 圓餅間距
                outerArc = d3
                  .arc()
                  .outerRadius(radius * 0.9)
                  .innerRadius(radius * 0.9);
      
              // 圓餅圖建構函式帶入資料
              const pieChartData = piechartGenerator(data);
      
              // 建立pie
              const expensesChart = svg
                .select(".slices")
                .selectAll("path")
                .data(pieChartData)
                .enter();
      
              // 綁定圓弧路徑
              expensesChart
                .append("path")
                .attr("d", arc)
                .attr("class", "arc")
                .attr("fill", color)
                .attr("stroke", "#fff")
                .style("stroke-width", "3px")
                .style("opacity", 1);
      
              // 加上每個區塊的數字標示 =========================
              // 計算每塊資料的百分占比,先用 d3.sum 加總全部資料,再將資料一一除上總數
              const total = d3.sum(data, (d) => d.data);
              data.forEach((i) => {
                i.percentage = Math.round((i.data / total) * 100);
              });
      
              // 調整數字標示的位置
              const textArc = d3
                .arc()
                .innerRadius(radius)
                .outerRadius(radius - 10);
      
              // 綁定數字標籤位置
              expensesChart
                .append("text")
                .attr("transform", (d) => `translate(${textArc.centroid(d)})`)
                .text((d) => d.data.item + d.data.percentage + "%")
                .style("text-anchor", "middle")
                .style("font-size", 16)
                .style("fill", "black");
      
      
      
            // 滑鼠互動
            d3.selectAll('.arc')
              .style('cursor', 'pointer')
              .on('mouseover',(d)=>{
                d3.select(d.target)
                  .transition()
                  .duration(500)
                  .style("filter", "drop-shadow(2px 4px 6px black)")
                  .style('transform', 'scale(1.1)')
              })
              .on('mouseleave', (d)=>{
                d3.select(d.target)
                  .transition()
                  .duration(500)
                  .style("filter", "drop-shadow(0 0 0 black)")
                  .style('transform', 'scale(1)')
               })
            }
      
            drawExpensesChartHover();
          
        
每月財務分配-1月、2月份資料切換

程式碼

          
            //html 
            <div class="d-flex justify-content-center">
              <button class="px-4 btn btn-warning me-2">
                1月
              </button>
              <button class="px-4 btn btn-warning ms-2">
                2月
              </button>
            </div>
            
            <div class="pieChartTransition"></div>
          
       
          
          //JS
          // pie svg
          const pieWidth = parseInt(
              d3.select(".pieChartTransition").style("width")
            ),
            pieHeight = 400,
            pieMargin = 40;
    
          const pieSvg = d3
            .select(".pieChartTransition")
            .append("svg")
            .attr("width", pieWidth)
            .attr("height", pieHeight);

          // 切換月份的按鈕
          const JanuaryBtn = d3.select('.januaryBtn')
          const FeburaryBtn = d3.select('.feburaryBtn')
    
          JanuaryBtn.on('click', (e,d)=>{
            // 按鈕切換
            JanuaryBtn.style('background-color', '#e09500');
            FeburaryBtn.style('background-color', '#ffc107');
    
            updatePieData(pieDataJanuary)
          });
    
          FeburaryBtn.on('click', (e,d)=>{
            // 按鈕切換
            FeburaryBtn.style('background-color', '#e09500');
            JanuaryBtn.style('background-color', '#ffc107');
    
            updatePieData(pieDataFeburary)
          })

          // 建立不同月份的資料集
          const pieDataJanuary = [
            { item: "交通", data: 3000 },
            { item: "房租", data: 12000 },
            { item: "日常用品", data: 1400 },
            { item: "吃飯", data: 4000 },
            { item: "交際應酬", data: 2400 },
          ];
    
          const pieDataFeburary = [
            { item: "交通", data: 1600 },
            { item: "房租", data: 12000 },
            { item: "日常用品", data: 2000 },
            { item: "吃飯", data: 2000 },
            { item: "交際應酬", data: 1600 },
          ];
      
          // 建立圓餅集合標籤g
          const pie = pieSvg
            .append("g")
            .attr("transform", 
                `translate(${pieWidth / 2}, ${pieHeight / 2})`
            );
    
          // 設定顏色
          const pieColor = d3
            .scaleOrdinal()
            .domain(["交通", "房租", "日常用品", "吃飯", "交際應酬"])
            .range(d3.schemeSet2);
    
          // 設定圓餅半徑
          const radius = Math.min(pieWidth, pieHeight) / 2 - pieMargin;
      
          // 設定更新資料的方法
          const updatePieData = (data) => {
              // 將資料綁定到圓餅圖上
              const piechartGenerator = d3
                .pie()
                .value((d) => d.data)
                // 固定圓餅圖的項目排序,確保資料抽換時群組順序保持一致
                .sort((a, b) => d3.ascending(a.key, b.key));
      
              // innerRadius 跟 outerRadius 設定圓餅內圈外圈的半徑
              const arc = d3
                  .arc()
                  .innerRadius(0)
                  .outerRadius(radius)
                  .padAngle(0);

              const outerArc = d3
                  .arc()
                  .outerRadius(radius * 0.9)
                  .innerRadius(radius * 0.9);
      
              // 設定圓餅圖建構函式
              const pieChartData = piechartGenerator(data);
      
              // 建立pie
              const expensesChart = pie.selectAll("path").data(pieChartData);
      
              // 將資料綁定圓弧路徑
              expensesChart
                .join("path")
                // 資料變化動畫
                .transition()
                .duration(1000)
                .attr("class", "slice")
                .attr("d", arc)
                .attr("fill", (d) => pieColor(d.data.item))
                .attr("stroke", "#fff")
                .style("stroke-width", "3px")
                .style("opacity", 1);
      
              // 加上每個區塊的數字標示
              // 計算每塊資料的百分占比,先用 d3.sum 加總全部資料,再將資料一一除上總數
              const total = d3.sum(data, (d) => d.data);
              data.forEach((i) => {
                i.percentage = Math.round((i.data / total) * 100);
              });
      
              // 調整數字標示的位置
              const textArc = d3
                .arc()
                .innerRadius(radius)
                .outerRadius(radius + 20);
      
              // 綁定數字標籤並設定位置
              const textLabel = pie
                .selectAll("text")
                .data(pieChartData)
                .join("text")
                .text((d) => d.data.item + d.data.percentage + "%")
                .transition()
                .duration(1000)
                .attr("transform", (d) => `translate(${textArc.centroid(d)})`)
                .style("text-anchor", "middle")
                .style("font-size", 16)
                .style("fill", "black");
          };
    
          updatePieData(pieDataJanuary);