Im relatively new to Spring and Thymeleaf but Ive managed to make a disc golf page showing the rounds played by a user with the courses and round scores shown in an accordion. Image below, I can dynamically add all the holes, pars, scores etc and it (so far) works good, but my javascript is still very much beginner.
I’m trying to add a simple horizontal stacked bar for each round, but so far I get only a chart on the first round, and most importantly, get the dynamic score data into the chart? Right now its hard coded in the javascript using the ‘const arr’.
So how can I have dynamic charts for each score card? Is it correct practice to pass the data using the th:attr?
Also I havent started trying to assign the color by score cause right now its hard coded. So like the score card, par is no color, 1 under par is blue, 1 over par is orange and a hole in one would be green.
html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link th:href="@{/css/fontawesome/css/all.css}" rel="stylesheet">
<link rel="stylesheet" type="text/css" th:href="@{/css/bootstrap/bootstrap.min.css}"/>
<link rel="stylesheet" th:href="@{https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css}">
<link rel="stylesheet" type="text/css" th:href="@{/css/discgolf/round.css}">
<title>Rounds</title>
</head>
<body>
<div th:replace="fragments/navbars/libraryNavbar :: navbar"></div>
<div class="container">
<div class="subContainer">
<h1>Rounds</h1>
<a class="col-sm-4" th:href="@{/discgolf}">Disc Golf Home</a>
<div class="row" id="username">
<b class="col-sm-8">Username:<span sec:authentication="principal.username"></span></b>
</div>
</div>
<a>Add Round</a>
<form action="#" th:action="@{/discgolf/newRound}" th:object="${course}"
method="GET">
<div class="form-group">
<div class="form-group blu-margin">
<select th:name="course" class="form-control" onchange="this.form.submit()">
<option th:value="0" th:text="${'Please Select'}"></option>
<option th:each="course : ${courses}"
th:text="${course.name}" >
</option>
</select>
</div>
</div>
</form>
</div>
<div class="container">
<div>
<a>Rounds Played</a>
</div>
<div >
<div th:each="roundCourse : ${rounds}" class="card">
<button class="accordion">
<span th:text="${roundCourse.key.name}"></span>
</button>
<div class="panel">
<th:block th:each="round : ${roundCourse.value}">
<div class="card-body">
<div class="row">
<div class="col-3">
<label>Date: </label>
<label th:text="${#dates.format(round.roundDate, 'dd-MMM-yyyy')}"></label>
</div>
<div class="col-3">
<label>Score: </label>
<label th:text="${round.total - round.course.par}"></label>
</div>
<div class="col-6">
<div class="container-fluid">
<canvas th:attr="data-counts=${round.scores}" id="myChart"></canvas>
</div>
</div>
</div>
<br>
<div >
<table id="courseInfo" class="table table-bordered w-auto">
<th:block th:each="course : ${round.course}">
<tr>
<th th:text="${'Hole'}"></th>
<th th:each="hole : ${course.holes}" th:text="${hole.number}"></th>
<th th:text="${'Total'}"></th>
</tr>
<tr>
<td th:text="${'Par'}"></td>
<td th:each="par : ${course.holes}" th:text="${par.par}"></td>
<td th:text="${course.par}"></td>
</tr>
<tr>
<td th:text="${'Score'}"></td>
<th:block th:each="score : ${round.scores}">
<td th:style="${score.score > score.holePar}
? 'background-color: red'
: (${score.score < score.holePar } ? 'background-color: blue'
: 'background-color: #eee' ) " th:text="${score.score}"></td>
</th:block>
<td th:text="${round.total}"></td>
</tr>
</th:block>
</table>
<br>
<a th:href="@{/discgolf/deleteRound/{id}(id=${round.roundId})}" title="Remove Course"
data-target="#deleteRoundModal" class="table-link danger" id="deleteRoundButton" >
<span id="deleteRound" class="fa-stack">
<i class="fa fa-square fa-stack-2x"></i>
<i class="fa fa-trash-o fa-stack-1x fa-inverse" title="Delete this round"></i>
</span>
</a>
</div>
</div>
<hr>
</th:block>
</div>
</div>
</div>
</div>
<script th:inline="javascript">
var listRounds = [[${rounds}]];
</script>
<script>
</script>
<!--<div th:replace="/discgolf/fragments/deleteCourseModal :: deleteCourseModal"></div>-->
<script type="text/javascript" src="/js/jquery-3.6.0.js"></script>
<script type="text/javascript" src="/js/bootstrap/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script th:src="@{/js/discgolf/userRounds.js}"></script>
</body>
</html>
javascript
const countsTest = document.getElementById('myChart').getAttribute('data-counts');
console.log("Counts Test: " + countsTest);
*** so the dynamic array would be here***
const arr = [3, 3, 4, 2, 2, 3, 3, 2, 4, 4];
const counts = {};
for (const num of arr) {
counts[num] = counts[num] ? counts[num] + 1 : 1;
}
console.log(counts);
console.log(counts[1], counts[2], counts[3], counts[4], counts[5]);
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";
}
});
}
new Chart(document.getElementById('myChart'),{
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[2]],
backgroundColor: "#77ACD8"
},{
data: [counts[3]]
// backgroundColor: "rgba(163,103,126,1)"
},{
data: [counts[4]],
backgroundColor: "#FDC169"
}]
}
}
);
Controller
@GetMapping("/rounds/{id}")
public String roundsHome(@PathVariable(value = "id") Long id,
Model model) {
List<Course> courses = courseService.getAllCourses();
List<Round> rounds = userService.getUserById(id).getRounds();
Map<Course, List<Round>> mapRoundsByCourse = rounds.stream().collect(Collectors.groupingBy(Round::getCourse));
model.addAttribute("courses", courses);
model.addAttribute("rounds", mapRoundsByCourse);
return "/discgolf/round/rounds";
}
Data
Course{id=2, name='Ilsede', holes=[Hole{holeId=46, number=1, par=3}, Hole{holeId=47, number=2, par=3}, Hole{holeId=48, number=3, par=3}, Hole{holeId=49, number=4, par=3}, Hole{holeId=50, number=5, par=3}, Hole{holeId=51, number=6, par=3}, Hole{holeId=52, number=7, par=3}, Hole{holeId=53, number=8, par=3}, Hole{holeId=54, number=9, par=3}, Hole{holeId=55, number=10, par=3}, Hole{holeId=56, number=11, par=3}, Hole{holeId=57, number=12, par=3}, Hole{holeId=58, number=13, par=4}, Hole{holeId=59, number=14, par=3}, Hole{holeId=60, number=15, par=3}, Hole{holeId=61, number=16, par=3}, Hole{holeId=62, number=17, par=3}, Hole{holeId=63, number=18, par=3}], par=55, record=7}
=[Round{roundId=21, course=Course{id=2, name='Ilsede', holes=[Hole{holeId=46, number=1, par=3}, Hole{holeId=47, number=2, par=3}, Hole{holeId=48, number=3, par=3}, Hole{holeId=49, number=4, par=3}, Hole{holeId=50, number=5, par=3}, Hole{holeId=51, number=6, par=3}, Hole{holeId=52, number=7, par=3}, Hole{holeId=53, number=8, par=3}, Hole{holeId=54, number=9, par=3}, Hole{holeId=55, number=10, par=3}, Hole{holeId=56, number=11, par=3}, Hole{holeId=57, number=12, par=3}, Hole{holeId=58, number=13, par=4}, Hole{holeId=59, number=14, par=3}, Hole{holeId=60, number=15, par=3}, Hole{holeId=61, number=16, par=3}, Hole{holeId=62, number=17, par=3}, Hole{holeId=63, number=18, par=3}], par=55, record=7}, scores=[Score{scoreId=199, score=3, holePar=3}, Score{scoreId=200, score=3, holePar=3}, Score{scoreId=201, score=3, holePar=3}, Score{scoreId=202, score=4, holePar=3}, Score{scoreId=203, score=3, holePar=3}, Score{scoreId=204, score=3, holePar=3}, Score{scoreId=205, score=2, holePar=3}, Score{scoreId=206, score=3, holePar=3}, Score{scoreId=207, score=3, holePar=3}, Score{scoreId=208, score=4, holePar=3}, Score{scoreId=209, score=3, holePar=3}, Score{scoreId=210, score=3, holePar=3}, Score{scoreId=211, score=2, holePar=3}, Score{scoreId=212, score=3, holePar=3}, Score{scoreId=213, score=3, holePar=3}, Score{scoreId=214, score=4, holePar=3}, Score{scoreId=215, score=3, holePar=3}, Score{scoreId=216, score=2, holePar=3}], roundDate=2023-03-01 00:00:00.0, total=54},
Round{roundId=24, course=Course{id=2, name='Ilsede', holes=[Hole{holeId=46, number=1, par=3}, Hole{holeId=47, number=2, par=3}, Hole{holeId=48, number=3, par=3}, Hole{holeId=49, number=4, par=3}, Hole{holeId=50, number=5, par=3}, Hole{holeId=51, number=6, par=3}, Hole{holeId=52, number=7, par=3}, Hole{holeId=53, number=8, par=3}, Hole{holeId=54, number=9, par=3}, Hole{holeId=55, number=10, par=3}, Hole{holeId=56, number=11, par=3}, Hole{holeId=57, number=12, par=3}, Hole{holeId=58, number=13, par=4}, Hole{holeId=59, number=14, par=3}, Hole{holeId=60, number=15, par=3}, Hole{holeId=61, number=16, par=3}, Hole{holeId=62, number=17, par=3}, Hole{holeId=63, number=18, par=3}], par=55, record=7}, scores=[Score{scoreId=244, score=3, holePar=3}, Score{scoreId=245, score=3, holePar=3}, Score{scoreId=246, score=3, holePar=3}, Score{scoreId=247, score=3, holePar=3}, Score{scoreId=248, score=4, holePar=3}, Score{scoreId=249, score=3, holePar=3}, Score{scoreId=250, score=3, holePar=3}, Score{scoreId=251, score=3, holePar=3}, Score{scoreId=252, score=2, holePar=3}, Score{scoreId=253, score=3, holePar=3}, Score{scoreId=254, score=3, holePar=3}, Score{scoreId=255, score=3, holePar=3}, Score{scoreId=256, score=2, holePar=3}, Score{scoreId=257, score=3, holePar=3}, Score{scoreId=258, score=3, holePar=3}, Score{scoreId=259, score=4, holePar=3}, Score{scoreId=260, score=3, holePar=3}, Score{scoreId=261, score=3, holePar=3}], roundDate=2023-03-09 00:00:00.0, total=54}]
Image of roundId = 21 (I know colors dont match yet)

Thanks in advance