Chartjs-Show dynamic horizontal bar chart in Thymeleaf for each

1👍

You can render you entire object rounds as JSON within a thymeleaf inline <script> tag

<script th:inline="javascript">
  let rounds = /*[[${rounds}]]*/ {};
</script>

So as per my codepen, the code remains the same

<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.2.1/dist/chart.umd.min.js"></script>
</head>
<body>
<div>
  <canvas id="myChart" width="600" height="250"  ></canvas>
</div>
<script th:inline="javascript">
  var datasets = [];  
  var label = [];
  var rounds = /*[[${roundsJson}]]*/ {};
  console.log(rounds);
  var i = 0;
  for(var prop in rounds) {
     var dataset = []
     for(var j=0;j<rounds[prop][0].scores.length;j++) {
        dataset.push(rounds[prop][0].scores[j].score)
     }
     console.log(dataset)
     const counts = {};
     for (const num of dataset) {
        counts[num] = counts[num] ? counts[num] + 1 : 1;
     }
     console.log(counts)
     label.push("Score Round " + (i+1))
     i++;
     datasets.push(counts)
  }
  
  console.log(datasets)
  
  var newdatasets = [];
  var keys = Object.keys(datasets[0])
  for(var j=0;j<keys.length;j++) {
     newdatasets.push({
       data: [],
       key: keys[j],
       label: "Score " + keys[j]
     });
  }
  for(var i=0;i<newdatasets.length;i++) {
     for(j=0;j<datasets.length;j++) {
        console.log(datasets[j][newdatasets[i].key]);
        newdatasets[i].data.push(datasets[j][newdatasets[i].key])
     }
  }
  
  console.log(newdatasets)

var ctx = document.getElementById("myChart").getContext("2d");
var myChart = new Chart(ctx, {
    type: 'bar',
    data: {
        labels: label,        
        datasets: newdatasets
    },
    options: {
        responsive: true,
        maintainAspectRatio: false,
        indexAxis: 'y',
        scales: {
          x: {
            stacked: true,
            display: false
          },
          y: {
            stacked: true,
            display: false
          }
        },
        plugins: {
          legend: {
            display: false
          }
        },
    }
});
</script>
</body>
</html>

modify the for loop and change the chart as per your needs

Regarding your class structure please use this code
To convert your class structure to plain JSON

try {
    ObjectMapper mapper = new ObjectMapper();
    Map<Course, List<Round>> mapRoundsByCourse = rounds.stream().collect(Collectors.groupingBy(Round::getCourse));
    JsonNode jsonNode = mapper.valueToTree(mapRoundsByCourse);
    model.addAttribute("roundsJson", jsonNode);

} catch (IOException e) {
          

Then change your <script> to

<script th:inline="javascript">
  let rounds = /*[[${roundsJson}]]*/ {};
</script>

0👍

So for anyone else having the same issue to create a chart in a thymeleaf loop with an accordion. I pass a list of rounds to the javascript, then in the loop pass the roundId. Then create a loop with the roundId’s with a chart inside and get the round if using the getRoundId method. Check above for data and more info.
Html

<th:block th:each="round : ${roundCourse.rounds}">
...
<div class="container-fluid">
   <canvas th:roundId="${round.barChartArray}" th:id="'myChart-' + ${round.roundId}"></canvas>
</div>
</th:block>
...
<script th:inline="javascript">
    let rounds = /*[[${roundsJsonNode}]]*/ {};
</script>

javascript

function getRoundById(rounds, roundId) {
      return rounds.find((round) => round.roundId === Number(roundId));
    }

    var acc = document.getElementsByClassName("accordion");
    var i;

    for (i = 0; i < acc.length; i++) {
      acc[i].addEventListener("click", function() {
        this.classList.toggle("active");
        var panel = this.nextElementSibling;
        if (panel.style.maxHeight) {
          panel.style.maxHeight = null;
        } else {
          panel.style.maxHeight = panel.scrollHeight + "px";
        }
      });
    }

    const charts = document.querySelectorAll('[roundId]');
      charts.forEach(chart => {
        const getRound = chart.getAttribute('roundId').split(',');
        const roundData = getRoundById(rounds, getRound);

        const scoreCount = roundData.scores.length;
        const scoreData = {};

        for (let i = 0; i < scoreCount; i++) {
          const score = roundData.scores[i];
          if (!scoreData[score.name]) {
            scoreData[score.name] = {
              count: 0,
              color: score.color,
              score: score.score,
            };
          }
          scoreData[score.name].count += 1;
        }

        const datasets = [];
        Object.entries(scoreData).forEach(([name, data]) => {
          datasets.push({
            label: name,
            backgroundColor: data.color,
            data: [data.count],
            score: data.score, // add score value to dataset object
          });
        });

        datasets.sort((a, b) => a.score - b.score); // sort datasets by score value

        const myChart = new Chart(chart, {
          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: [''],
            datasets,
          },
        });
      });

Leave a comment