【问题标题】:Update data of chartjs chart更新chartjs图表的数据
【发布时间】:2021-09-14 12:29:16
【问题描述】:

首先,我想说我是一个学生学习编程大约一个月,所以期待看到很多错误。

我正在使用 ChartJs 库中的图表的网站上工作。此图表使用的数据是通过对服务器的请求获取的。我正在努力每隔 X 秒向服务器发出请求,因此图表上显示的数据会自动更新,而无需刷新或执行任何操作。

我想说我已经完成了大部分工作,但我对 ChartJs 有疑问。我有图表和对服务器的请求,都在一个函数中,然后我调用windows.onload。我使用setInterval 每 5 秒调用一次此函数,实际上这有效,但我收到此错误:

未捕获的错误:画布已在使用中。 ID 为“0”的图表必须是 在画布可以重复使用之前被销毁。

所以我明白发生了什么,我试图在同一个canvas 元素上一遍又一遍地创建图表,这当然行不通。我已经看到了destroy() 方法和update() 方法,但是我还没有找到适合我的具体情况的解决方案。如果有人能告诉我在这种情况下如何做到这一点的任何想法,我将不胜感激。代码如下:

let serverData;
let stundenGesamt;
let date;
const url = 'https://urlsample.de/'; // Hidden the actual URL as it is the actual server from my company
const chart = document.getElementById("multie-pie-chart");

// Function that calculates the workdays passed up until today
const workdaysCount = () => 
[...new Array(new Date().getDate())]
.reduce((acc, _, monthDay) => {
  const date = new Date()
  date.setDate(1+monthDay)    
  ![0, 6].includes(date.getDay()) && acc++
  return acc      
  }, 0)




window.onload = function() {
  chartRender(); // Calling the function that renders the chart
  setInterval(chartRender, 5000); // Rendering the chart every 5 seconds

};



let chartRender = () => {

  console.log('test');

  let http = new XMLHttpRequest();
  http.open("GET", url);
  http.setRequestHeader('key', 'key-sample'); // Hidden the actual key as it is from the actual server from my company

  http.onload = () => {

    // Parsing the JSON file and storing it into a variable (Console.Log() to make sure it works)
    serverData = JSON.parse(http.responseText);
    console.log(serverData);

    stundenGesamt = serverData.abzurechnen.gesamt; // Storing the value of total hours from the database in a variable
  
    Chart.register(ChartDataLabels);
    
    // Basic UI of the pie chart
    const data = {
      labels: ['Summe', 'Noch um Ziel zu erreichen', 'Arbeitstage', 'Verbleibende Tage im Monat'],
      datasets: [
        {
          backgroundColor: ['#5ce1e6', '#2acaea'],
          data: [stundenGesamt, (800 - stundenGesamt)]
        },
        {
          backgroundColor: ['#cd1076', '#8b0a50'],
          data: [workdaysCount(), (22 - workdaysCount())] 
        },
      ]
};

    // Configuration of the pie chart
    let outterChart = new Chart(chart, {
      type: 'pie',
      data: data,
      options: {
      responsive: true,
      plugins: {
        datalabels: {
          font: {
            weight: 'bold',
            size: 20
          },
          color: 'white',
          formatter: (val, chart) => {
            const totalDatasetSum = chart.chart.data.datasets[chart.datasetIndex].data.reduce((a, b) => (a + b), 0);
            const percentage = val * 100 / totalDatasetSum;
            const roundedPercentage = Math.round(percentage * 100) / 100
            return `${roundedPercentage}%`
          }
        },
        legend: {
          labels: {
            color: 'white',
              font: {
                size: 14,
                family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
                weight: 'bold'
              },
              generateLabels: function(chart) {
              // Get the default label list
              const original = Chart.overrides.pie.plugins.legend.labels.generateLabels;
              const labelsOriginal = original.call(this, chart);

              // Build an array of colors used in the datasets of the chart
              var datasetColors = chart.data.datasets.map(function(e) {
              return e.backgroundColor;
            });
              datasetColors = datasetColors.flat();

              // Modify the color and hide state of each label
              labelsOriginal.forEach(label => {

              // Change the color to match the dataset
              label.fillStyle = datasetColors[label.index];
            });

            return labelsOriginal;
          }
        },
        onClick: function(mouseEvent, legendItem, legend) {
          // toggle the visibility of the dataset from what it currently is
          legend.chart.getDatasetMeta(
            legendItem.datasetIndex
          ).hidden = legend.chart.isDatasetVisible(legendItem.datasetIndex);
          legend.chart.update();
        }
      },
      tooltip: {
        callbacks: {
          label: function(context) {
            const labelIndex = (context.datasetIndex * 2) + context.dataIndex;
            return context.chart.data.labels[labelIndex] + ': ' + context.formattedValue;
          }
        }
      },
    }
  },
});
};

http.send();
};

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="style.css">
    <title>Redmine Monitor</title>
</head>
<body>
    <div class="container">
        <canvas id="multie-pie-chart" height="200" width="200"></canvas>
            
            <div class="titles">
                <h3>Ziel: 800 Stunden</h3>
                <h3>Monat: September</h3>
            </div>
    </div>




<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.min.js"></script>
<script src="script.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.0.0/chartjs-plugin-datalabels.js"></script>


</body>
</html>

let serverData;
let stundenGesamt;
let date;
let outterChart;
const chart = document.getElementById("multie-pie-chart");

// Function that calculates the workdays passed up until today
const workdaysCount = () => 
[...new Array(new Date().getDate())]
.reduce((acc, _, monthDay) => {
  const date = new Date()
  date.setDate(1+monthDay)    
  ![0, 6].includes(date.getDay()) && acc++
  return acc      
  }, 0)




window.onload = function() {
  chartRender(); // Calling the function that renders the chart
  // setInterval(chartRender, 5000); // Rendering the chart every 5 seconds

};



let chartRender = () => {


  
    Chart.register(ChartDataLabels);
    
    // Basic UI of the pie chart
    const data = {
      labels: ['Summe', 'Noch um Ziel zu erreichen', 'Arbeitstage', 'Verbleibende Tage im Monat'],
      datasets: [
        {
          backgroundColor: ['#5ce1e6', '#2acaea'],
          data: [476.5, (800 - 476.5)] //Instead of 476.5, it would be the variable stundenGesamt, which contains the number of hours worked until this moment, which has been extracted from the server through the JSON file that has been parsed into the serverData variable. These variables are not available in the code snippet as I had to remove part of the codes to make it work, but you can see them on the code I posted above, which is the most accurante one. This code snippet is only used to display the graph and how it is structured
        },
        {
          backgroundColor: ['#cd1076', '#8b0a50'],
          data: [workdaysCount(), (22 - workdaysCount())] 
        },
      ]
};

    // Configuration of the pie chart
    outterChart = new Chart(chart, {
      type: 'pie',
      data: data,
      options: {
      responsive: true,
      plugins: {
        datalabels: {
          font: {
            weight: 'bold',
            size: 20
          },
          color: 'white',
          formatter: (val, chart) => {
            const totalDatasetSum = chart.chart.data.datasets[chart.datasetIndex].data.reduce((a, b) => (a + b), 0);
            const percentage = val * 100 / totalDatasetSum;
            const roundedPercentage = Math.round(percentage * 100) / 100
            return `${roundedPercentage}%`
          }
        },
        legend: {
          labels: {
            color: 'white',
              font: {
                size: 14,
                family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
                weight: 'bold'
              },
              generateLabels: function(chart) {
              // Get the default label list
              const original = Chart.overrides.pie.plugins.legend.labels.generateLabels;
              const labelsOriginal = original.call(this, chart);

              // Build an array of colors used in the datasets of the chart
              var datasetColors = chart.data.datasets.map(function(e) {
              return e.backgroundColor;
            });
              datasetColors = datasetColors.flat();

              // Modify the color and hide state of each label
              labelsOriginal.forEach(label => {

              // Change the color to match the dataset
              label.fillStyle = datasetColors[label.index];
            });

            return labelsOriginal;
          }
        },
        onClick: function(mouseEvent, legendItem, legend) {
          // toggle the visibility of the dataset from what it currently is
          legend.chart.getDatasetMeta(
            legendItem.datasetIndex
          ).hidden = legend.chart.isDatasetVisible(legendItem.datasetIndex);
          legend.chart.update();
        }
      },
      tooltip: {
        callbacks: {
          label: function(context) {
            const labelIndex = (context.datasetIndex * 2) + context.dataIndex;
            return context.chart.data.labels[labelIndex] + ': ' + context.formattedValue;
          }
        }
      },
    }
  },
});
};
body {
    font-family: Arial, Helvetica, sans-serif;
    background-color: #343E59;

  }

.container {
    display: flex;
    width: 800px;
    margin: auto;
    flex-direction: column-reverse;
    justify-content: center;
    color: white;
}

.titles {
    display: flex;
    justify-content: space-evenly;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="style.css">
    <title>Redmine Monitor</title>
</head>
<body>
    <div class="container">
        <canvas id="multie-pie-chart" height="200" width="200"></canvas>
            
            <div class="titles">
                <h3>Ziel: 800 Stunden</h3>
                <h3>Monat: September</h3>
            </div>
    </div>




<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.min.js"></script>
<script src="script.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.0.0/chartjs-plugin-datalabels.js"></script>


</body>
</html>

【问题讨论】:

    标签: javascript canvas graph charts


    【解决方案1】:

    第一次运行函数时,您将图表(又名 new Chart() 的返回值”)放入 outterChart 变量中。

    如果您在函数之外(在这种情况下,在 chartRender 之外)实例化该变量,其值将在函数的多次运行中保留,您将能够选择是否创建图表或者只是根据outterChart 的当前值更新它。

    原则上:

    let outterChart; // undefined at first...
    
    let chartRender = () => {
      ...
      http.onLoad = () => {
        ...
    
        if (outterChart) {
          // update outterChart here. it has already been created
    
          /* 
           * The update syntax depends on the structure of the data returned by server
           * docs here: https://www.chartjs.org/docs/3.5.0/developers/updates.html
           */
    
        } else {
          outterChart = new Chart({
            ...
          });
        }
      }
    }
    

    编辑:查看您的 mcve 后,这是您需要的部分:

      ...
      if (outterChart) {
        data.datasets.forEach((ds, i) => {
          outterChart.data.datasets[i].data = ds.data;
        })
        outterChart.update();
      } else {
        outterChart = new Chart(chart, { ... })
      }
    ...
    

    我本来可以做的:

    if (outterChart) {
      outterChart.data = data;
    } else {
      outterChart = new Chart(chart, { ... })
    }
    

    ...,但这将完全替换您的数据集,而不是更新它们的数据。如果您替换任何当前数据集,图表将执行 "enter" 动画(将从头开始制作动画);而如果您只替换数据集的 data,它将从当前值动画到新值。

    看看效果

    let serverData;
    let stundenGesamt = 476.5;
    let date;
    let outterChart;
    const chart = document.getElementById("multie-pie-chart");
    
    // Function that calculates the workdays passed up until today
    const workdaysCount = () =>
      [...new Array(new Date().getDate())]
        .reduce((acc, _, monthDay) => {
          const date = new Date()
          date.setDate(1+monthDay)
          ![0, 6].includes(date.getDay()) && acc++
          return acc
        }, 0)
    
    
    
    
    window.onload = function() {
      chartRender(); // Calling the function that renders the chart
      setInterval(() => {
        // randomizing stundenGesamt so it generates different values.
        // instead, you get the value from server and put it into `studentGesamt`
        stundenGesamt = Math.floor(Math.random() * (600 - 200 + 1) + 200);
    
        // and then call `chartRender()`
        chartRender();
      }, 5000); // Rendering the chart every 5 seconds
    
    };
    
    
    
    let chartRender = () => {
    
      Chart.register(ChartDataLabels);
    
      // Basic UI of the pie chart
      const data = {
        labels: ['Summe', 'Noch um Ziel zu erreichen', 'Arbeitstage', 'Verbleibende Tage im Monat'],
        datasets: [
          {
            backgroundColor: ['#5ce1e6', '#2acaea'],
            // use current value of stundenGesamt in data
            data: [stundenGesamt, (800 - stundenGesamt)] //Instead of 476.5, it would be the variable stundenGesamt, which contains the number of hours worked until this moment, which has been extracted from the server through the JSON file that has been parsed into the serverData variable. These variables are not available in the code snippet as I had to remove part of the codes to make it work, but you can see them on the code I posted above, which is the most accurante one. This code snippet is only used to display the graph and how it is structured
          },
          {
            backgroundColor: ['#cd1076', '#8b0a50'],
            data: [workdaysCount(), (22 - workdaysCount())]
          },
        ]
      };
      if (outterChart) {
        data.datasets.forEach((ds, i) => {
          outterChart.data.datasets[i].data = ds.data;
        })
        outterChart.update();
      } else {
        outterChart = new Chart(chart, {
          type: 'pie',
          data: data,
          options: {
            responsive: true,
            plugins: {
              datalabels: {
                font: {
                  weight: 'bold',
                  size: 20
                },
                color: 'white',
                formatter: (val, chart) => {
                  const totalDatasetSum = chart.chart.data.datasets[chart.datasetIndex].data.reduce((a, b) => (a + b), 0);
                  const percentage = val * 100 / totalDatasetSum;
                  const roundedPercentage = Math.round(percentage * 100) / 100
                  return `${roundedPercentage}%`
                }
              },
              legend: {
                labels: {
                  color: 'white',
                  font: {
                    size: 14,
                    family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
                    weight: 'bold'
                  },
                  generateLabels: function (chart) {
                    // Get the default label list
                    const original = Chart.overrides.pie.plugins.legend.labels.generateLabels;
                    const labelsOriginal = original.call(this, chart);
    
                    // Build an array of colors used in the datasets of the chart
                    var datasetColors = chart.data.datasets.map(function (e) {
                      return e.backgroundColor;
                    });
                    datasetColors = datasetColors.flat();
    
                    // Modify the color and hide state of each label
                    labelsOriginal.forEach(label => {
    
                      // Change the color to match the dataset
                      label.fillStyle = datasetColors[label.index];
                    });
    
                    return labelsOriginal;
                  }
                },
                onClick: function (mouseEvent, legendItem, legend) {
                  // toggle the visibility of the dataset from what it currently is
                  legend.chart.getDatasetMeta(
                    legendItem.datasetIndex
                  ).hidden = legend.chart.isDatasetVisible(legendItem.datasetIndex);
                  legend.chart.update();
                }
              },
              tooltip: {
                callbacks: {
                  label: function (context) {
                    const labelIndex = (context.datasetIndex * 2) + context.dataIndex;
                    return context.chart.data.labels[labelIndex] + ': ' + context.formattedValue;
                  }
                }
              },
            }
          }
        });
      }
    };
    body {
        font-family: Arial, Helvetica, sans-serif;
        background-color: #343E59;
    
      }
    
    .container {
        display: flex;
        width: 800px;
        margin: auto;
        flex-direction: column-reverse;
        justify-content: center;
        color: white;
    }
    
    .titles {
        display: flex;
        justify-content: space-evenly;
    }
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" type="text/css" href="style.css">
        <title>Redmine Monitor</title>
    </head>
    <body>
        <div class="container">
            <canvas id="multie-pie-chart" height="200" width="200"></canvas>
                
                <div class="titles">
                    <h3>Ziel: 800 Stunden</h3>
                    <h3>Monat: September</h3>
                </div>
        </div>
    
    
    
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.min.js"></script>
    <script src="script.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.0.0/chartjs-plugin-datalabels.js"></script>
    
    
    </body>
    </html>

    【讨论】:

    • 我或多或少明白你的意思,但我无法让它工作(可能是因为我做得不对)。我很想提供一个可运行的示例,但我不知道在这种情况下该怎么做。我从我公司的服务器中获取此信息,我不允许共享这些信息,并且对于标头密钥也是如此。我假设手动添加这些数据与它当前的工作方式不同。关于我应该如何做的任何想法?
    • @Pauliecode,我可以自己制作minimal reproducible example,但我需要您数据的最小子集。只需通过更改名称和数字来匿名化它。我只关心结构以及它应该如何在图表中显示。一旦我有了它,我就可以创建一个工作示例来举例说明上述内容。我还需要知道您使用的是 ChartJS 2.x 还是 3.x,因为内部 API 在 2 和 3 版本之间发生了变化。
    • 我的意思是 http.load() 方法可以被模拟为调用本地函数而不是一些远程 API,但我需要知道你从 API 获得的响应看起来如何就像为了向您展示实际工作的代码。
    • 我添加了一个带有图形工作模型的代码 sn-p。我花了一段时间才让它工作,因为它一直给我错误,我在删除和匿名信息时没有在我的 IDE 上得到。我不得不从代码 sn-p 中删除与服务器和请求相关的所有内容,但是您可以在我开始发布的代码中看到带有请求的原始代码。这个应该正确显示图表的结构以及我想如何显示信息,我希望这是你要求的
    • 用一个工作示例更新了我的答案,@Paulie。编码愉快!
    【解决方案2】:

    取决于您的版本,但在 Chart JS 中执行此操作的方法是更新数据集并调用我们图表对象的更新方法。

    像这样:

    this.chart.data.datasets = data; // assuming your chart already exists
    this.chart.update();
    

    同样,您使用的 Chart JS 版本很重要,因为如迁移指南中所述,V2 和 V3 之间发生了重大变化 https://www.chartjs.org/docs/latest/getting-started/v3-migration.html

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-25
      • 2019-01-03
      • 1970-01-01
      • 2018-02-13
      • 1970-01-01
      相关资源
      最近更新 更多