当图例项变为 off/on 时,如何在 charts.js 饼图中重新分配值?

How can I re-distribute values within a charts.js pie chart when a legend item is turned off/on?

我已经将数据分布在整个饼图中,总计等于 100%(我认为大声笑)如果您不介意,请验证这是正确的。但是我希望能够在我转动 off/on 图例项时重新分发数据。

这是组件的示例

import React from "react";
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
import { Pie } from "react-chartjs-2";
import ChartDataLabels from "chartjs-plugin-datalabels";

ChartJS.register(ChartDataLabels, ArcElement, Tooltip, Legend);

export const data = [
  { lifetime_viewing_subs: 11497117, name: "US" },
  { lifetime_viewing_subs: 3777651, name: "LATAM" },
  { lifetime_viewing_subs: 2494138, name: "EMEA" }
];

const lifetime_viewing_subs_total = data.map(item => item.lifetime_viewing_subs).reduce((prev, next) => prev + next);

export const pieoptions = {
  responsive: true,
  plugins: {
    legend: {
      position: "bottom",
      align: "center"
    },
    datalabels: {
      color: function (context) {
        return context.dataset.borderColor;
      },
      anchor: "center",
      backgroundColor: function (context) {
        return context.dataset.background;
      },
      borderRadius: 100,
      borderWidth: 2,
      borderColor: function (context) {
        return context.dataset.borderColor;
      },
      display: function (context) {
        var dataset = context.dataset;
        var value = dataset.data[context.dataIndex];
        console.log("context.dataIndex: ", context.dataIndex); // 0,1,2
        console.log("dataset: ", dataset); // the dataset object
        console.log("value: ", value); // 0.1403653100534158, 0.647035726341284, 0.21259896360530017
      
        return value;
      },
      formatter: function (value) {
        return Math.round(value * 100).toFixed(0) + "%";
      },
      font: {
        weight: 500,
        size: "14rem"
      }
    }
  }
};

export const piedata = {
  labels: data.map((s) => s.name),
  datasets: [
    {
      data: data.map((s) => s.lifetime_viewing_subs/lifetime_viewing_subs_total),
      backgroundColor: [
        "rgba(255, 99, 132, 0.2)",
        "rgba(54, 162, 235, 0.2)",
        "rgba(255, 206, 86, 0.2)"
      ],
      borderColor: [
        "rgba(255, 99, 132, 1)",
        "rgba(54, 162, 235, 1)",
        "rgba(255, 206, 86, 1)"
      ],
      fill: true,
      borderWidth: 1
    }
  ]
};

export function App() {
  return (
    <Pie options={pieoptions} plugins={[ChartDataLabels]} data={piedata} />
  );
}

Here is a playground

提前致谢

您可以像这样在格式化程序函数中计算百分比:

formatter: function(value, ctx) {
  const max = ctx.dataset.data.reduce((acc, curr, index) => {
    const isVisible = ctx.chart.getDataVisibility(index)

    if (isVisible) {
      acc += curr;
    }

    return acc;
  }, 0)

  return Math.round(value * 100 / max).toFixed(0) + "%";
},

实例:

Chart.register(ChartDataLabels);


const data = [{
    lifetime_viewing_subs: 11497117,
    name: "US"
  },
  {
    lifetime_viewing_subs: 3777651,
    name: "LATAM"
  },
  {
    lifetime_viewing_subs: 2494138,
    name: "EMEA"
  }
];

const lifetime_viewing_subs_total = data
  .map((item) => item.lifetime_viewing_subs)
  .reduce((prev, next) => prev + next);

const options = {
  type: 'pie',
  data: {
    labels: data.map((s) => s.name),
    datasets: [{
      label: '# of Votes',
      data: data.map((s) => s.lifetime_viewing_subs / lifetime_viewing_subs_total),
      backgroundColor: [
        "rgba(255, 99, 132, 0.2)",
        "rgba(54, 162, 235, 0.2)",
        "rgba(255, 206, 86, 0.2)"
      ],
      borderColor: [
        "rgba(255, 99, 132, 1)",
        "rgba(54, 162, 235, 1)",
        "rgba(255, 206, 86, 1)"
      ],
      borderWidth: 1
    }]
  },
  options: {
    plugins: {
      legend: {
        position: "bottom",
        align: "center"
      },
      datalabels: {
        color: function(context) {
          return context.dataset.borderColor;
        },
        anchor: "center",
        backgroundColor: function(context) {
          return context.dataset.background;
        },
        borderRadius: 100,
        borderWidth: 2,
        borderColor: function(context) {
          return context.dataset.borderColor;
        },
        display: function(context) {
          var dataset = context.dataset;
          var value = dataset.data[context.dataIndex];

          return value;
        },
        formatter: function(value, ctx) {
          const max = ctx.dataset.data.reduce((acc, curr, index) => {
            const isVisible = ctx.chart.getDataVisibility(index)

            if (isVisible) {
              acc += curr;
            }

            return acc;
          }, 0)

          return Math.round(value * 100 / max).toFixed(0) + "%";
        },
        font: {
          weight: 500,
          size: "14rem"
        }
      }
    }
  }
}

var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
  <canvas id="chartJSContainer" width="600" height="400"></canvas>
  <script src="https://cdn.jsdelivr.net/npm/chart.js/dist/chart.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels"></script>
</body>

我不明白你想要 re-distribute 的逻辑是什么,但无论如何我为 re-distribute 数据写了一些逻辑,当你 turn off/on 图例项。

import React from "react";
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
import { Pie } from "react-chartjs-2";
import ChartDataLabels from "chartjs-plugin-datalabels";

ChartJS.register(ChartDataLabels, ArcElement, Tooltip, Legend);

export const data = [
  { lifetime_viewing_subs: 11497117, name: "US" },
  { lifetime_viewing_subs: 3777651, name: "LATAM" },
  { lifetime_viewing_subs: 2494138, name: "EMEA" }
];
var defaultLegendClickHandler = ChartJS.defaults.plugins.legend.onClick;

const lifetime_viewing_subs_total = data
  .map((item) => item.lifetime_viewing_subs)
  .reduce((prev, next) => prev + next);
export const pieoptions = {
  responsive: true,
  plugins: {
    legend: {
      onClick(event, legendItem, legend) {
        let index = legendItem.index;
        let ci = legend.chart;
        let tmpData;
       
        legend.chart.data.datasets.forEach((d, i) => {
          let dividerSize =  d.data.length;
          console.log(d)
          d.data.forEach((pie, j) => {
            if (index === j) {
              // clicked item is it
              tmpData = d.data[index];
              // you can define here what you want 
              // for clicked label
              // d.data[j] = 0.20;
              // curently i will clear value
              d.data[j] = 0;
            } else {
              // here you can work on other pieces
              // you can update next two pieces
              // d.data[j] = d.data[j] + 0.22
                d.data[j] = pie + 0.2
            }
            // 
            
          });
        });
        ci.update();
      },
      position: "bottom",
      align: "center"
    },
    datalabels: {
      color: function (context) {
        return context.dataset.borderColor;
      },
      anchor: "center",
      backgroundColor: function (context) {
        return context.dataset.background;
      },
      borderRadius: 100,
      borderWidth: 2,
      borderColor: function (context) {
        return context.dataset.borderColor;
      },
      display: function (context) {
        var dataset = context.dataset;
        var value = dataset.data[context.dataIndex];
        // console.log("context.dataIndex: ", context.dataIndex); // 0,1,2
        // console.log("dataset: ", dataset); // the dataset object
        // console.log("value: ", value); // 0.1403653100534158, 0.647035726341284, 0.21259896360530017

        return value;
      },
      formatter: function (value) {
        return Math.round(value * 100).toFixed(0) + "%";
      },
      font: {
        weight: 500,
        size: "14rem"
      }
    }
  }
};

export const piedata = {
  labels: data.map((s) => s.name),
  datasets: [
    {
      data: data.map(
        (s) => s.lifetime_viewing_subs / lifetime_viewing_subs_total
      ),
      backgroundColor: [
        "rgba(255, 99, 132, 0.2)",
        "rgba(54, 162, 235, 0.2)",
        "rgba(255, 206, 86, 0.2)"
      ],
      borderColor: [
        "rgba(255, 99, 132, 1)",
        "rgba(54, 162, 235, 1)",
        "rgba(255, 206, 86, 1)"
      ],
      fill: true,
      borderWidth: 1
    }
  ]
};

export function App() {
  return (
    <Pie options={pieoptions} plugins={[ChartDataLabels]} data={piedata} />
  );
}

我已经覆盖了 plugins/legend/onclick 方法,像这样

plugins: {
    legend: {
      onClick(event, legendItem, legend) {
        let index = legendItem.index;
        let ci = legend.chart;
        let tmpData;

        legend.chart.data.datasets.forEach((d, i) => {
          let dividerSize =  d.data.length;
          console.log(d)
          d.data.forEach((pie, j) => {
            if (index === j) {
              // clicked item is it
              tmpData = d.data[index];
              // you can define here what you want 
              // for clicked label
              // d.data[j] = 0.20;
              // curently i will clear value
              d.data[j] = 0;
            } else {
              // here you can work on other pieces
              // you can update next two pieces
              // d.data[j] = d.data[j] + 0.22
                d.data[j] = pie + 0.2
            }
            // 

          });
        });
        ci.update();
      }

你可以 redesign 随心所欲地操作,工作示例 playground