Chartjs-Thymeleaf th:each rendering a chart js bar chart in each loop

0👍

Here is a minimal reproducible example which focuses on one specific problem: displaying multiple charts in one page, where each chart uses its relevant data.

Some assumptions:

  1. There is no Thymeleaf or Java code, because the objective, as stated above, is only to draw multiple charts. The chart code runs after all Thymeleaf and Java has finished – and the page only contains HTML and JavaScript. So, all of that prior processing is not relevant here.

  2. I have assumed that the Thymeleaf generates HTML which contains only 2 charts. Obviously your Thymeleaf loop will generate more chart HTML. But again this is a MRE – it contains nothing which is not relevant to the specific problem.

  3. I do not know what the data looks like for data-counts=${roundService.getListOfScoresByRoundId(round.roundId)} – that is missing from the question (or I missed it, if it is in there somewhere). Therefore I have provided my own data: data-counts="[[1,2,3],[4,5,6]]". This is almost certainly not the same as your data. So you may need to take that into account… and adjust my approach for that.

  4. My JavaScript code loops through every chart in the page, and extracts the relevant data array from [[1,2,3],[4,5,6]]. I have 2 charts – so there are 2 data arrays: [1,2,3] for the first chart and [4,5,6] for the second chart. Yes, this is all hard-coded data (as per my MRE). It assumes your Thymeleaf and Java code provides this data correctly.

  5. You can uncomment the JavaScript console.log() statements to see what the code is doing, as it loops through each chart.

Enough already. Here it is – just click the blue run button:

const charts = document.getElementsByClassName('myChart');

for (let i = 0; i < charts.length; i++) {

  const countsTest = charts[i].getAttribute('data-counts');
  //console.log( charts[i] );
  //console.log( JSON.parse( countsTest ) );
  counts = JSON.parse(countsTest)[i];
  //console.log( counts );

  new Chart(charts[i], {
    type: 'bar',
    options: {
      responsive: true,
      maintainAspectRatio: false,
      indexAxis: 'y',
      scales: {
        x: {
          stacked: true,
          display: false
        },
        y: {
          stacked: true,
          display: false
        }
      },
      plugins: {
        legend: {
          display: false
        }
      },
    },

    data: {
      labels: ["Score"],

      datasets: [{
        data: [counts[0]],
        backgroundColor: "#77ACD8"
      }, {
        data: [counts[1]]
      }, {
        data: [counts[2]],
        backgroundColor: "#FDD79C"
      }]
    }
  });

}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <title>Rounds</title>
</head>

<body>

  <div>
    <canvas data-counts="[[1,2,3],[4,5,6]]" class="myChart"></canvas>
  </div>

  <div>
    <canvas data-counts="[[1,2,3],[4,5,6]]" class="myChart"></canvas>
  </div>

</body>

</html>

You can see that this approach contains the same chart data, repeated multiple times in the HTML: [[1,2,3],[4,5,6]].

In other words, you are provide all the chart data for all charts in each chart tag.

Really, you should refactor that to only provide the relevant chart data in each chart:

<div>
    <canvas data-counts="[1,2,3]" class="myChart"></canvas>
</div>

<div>
    <canvas data-counts="[4,5,6]" class="myChart"></canvas>
</div>

You may want to look at making this change after you get what you have to work.


My apologies if this is not what you need, or is not helpful.

But at least it may give you some pointers – and it shows what I mean by a MRE. A good MRE is focused, limited in scope, and is easy for us to copy/paste and run for ourselves.

Leave a comment