Add graph

This commit is contained in:
Laurent
2025-03-22 14:56:26 +01:00
parent ba31dd3d05
commit 577603df64
5 changed files with 152 additions and 46 deletions

View File

@@ -1,52 +1,118 @@
<script setup lang="ts"> <script setup lang="ts">
import {onMounted} from "vue"; import {onMounted, ref} from "vue";
import {Chart, type ChartItem} from "chart.js/auto";
import {Event} from "@/models/Event.ts"; import {Event} from "@/models/Event.ts";
import {AttendanceGraph} from "@/models/AttendanceGraph.ts";
import type {Attendee} from "@/models/Attendee.ts";
const props = defineProps<{ const props = defineProps<{
event: Event; event: Event;
}>(); }>();
let data = props.event.getDates(); let graph = new AttendanceGraph(props.event);
onMounted(() => { onMounted(() => {
draw() render();
console.log(props.event);
}) })
function draw() { function render() : void{
const ctx = document.getElementById('myChart'); let container = queryContainer();
if(!ctx) return; for (let date of graph.getDates()) {
let attendees: Attendee[] = graph.getAttendees(date);
new Chart(ctx as ChartItem, { let bar = addBar(container);
type: 'doughnut', setBarSize(bar, graph.getRatio(date));
data : { setBarTooltip(bar, graph.getAttendees(date).length);
labels: data.dates,
datasets: [{
label: 'Attendances',
data: data.attendances,
hoverOffset: 4
}]
} }
}
function addBar(container : HTMLElement) : HTMLElement {
let bar = document.createElement("div");
bar.classList.add("bar");
container.appendChild(bar);
return bar;
}
function setBarSize(bar : HTMLElement, ratio : number) : void{
console.log(ratio);
bar.style.width = `${ratio*100}%`;
}
function setBarTooltip(bar : HTMLElement, attendeesCount : number) : void{
let tooltip = document.createElement('div');
tooltip.classList.add('tooltip');
tooltip.innerText = attendeesCount.toString();
tooltip.style.position = 'absolute';
tooltip.style.visibility = 'hidden'; // Initially hidden
tooltip.style.backgroundColor = 'black';
tooltip.style.color = 'white';
tooltip.style.padding = '5px';
tooltip.style.borderRadius = '5px';
bar.addEventListener('mouseenter', function() {
document.body.appendChild(tooltip);
const rect = bar.getBoundingClientRect();
tooltip.style.left = `${rect.left + window.scrollX}px`;
tooltip.style.top = `${rect.top + window.scrollY - tooltip.offsetHeight - 5}px`;
tooltip.style.visibility = 'visible';
});
bar.addEventListener('mouseleave', function() {
tooltip.style.visibility = 'hidden';
tooltip.remove();
}); });
} }
function queryContainer() : HTMLElement {
return document.getElementsByClassName("graph")[0] as HTMLElement;
}
</script> </script>
<template> <template>
<div class="attendance-graph"> <div class="attendance-graph">
<canvas id="myChart"></canvas> <h2>{{event.getName()}}</h2>
<div class="graph"></div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
canvas { .graph {
display: flex;
flex-direction: column;
width: 100%; width: 100%;
height: 100%; height: 100%;
gap: 10px;
border-left: solid 1px black;
border-bottom: solid 1px black;
} }
::v-deep(.bar)
{
height : 30px;
border : solid 1px black;
background-color: var(--secondary-color);
flex-grow: 1;
}
::v-deep(.bar):hover
{
transform: scale(1.1);
}
.tooltip {
position: absolute;
background-color: black;
padding: 5px;
border-radius: 5px;
visibility: hidden;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip.visible {
visibility: visible;
opacity: 1;
}
</style> </style>

View File

@@ -0,0 +1,28 @@
import type {Event} from "@/models/Event.ts";
import type {Attendee} from "@/models/Attendee.ts";
export class AttendanceGraph {
private event: Event;
private maxAttendees: number;
public constructor(event : Event) {
this.event = event;
this.maxAttendees = event.getMaxAttendees();
console.log(this.maxAttendees);
}
public getDates() : number[] {
return Array.from(this.event.getDates());
}
public getAttendees(date : number): Attendee[] {
return this.event.getAttendees(date);
}
public getRatio(date : number) : number{
return (this.getAttendees(date).length / this.maxAttendees);
}
}

View File

@@ -21,19 +21,25 @@ export class Event {
return this.token; return this.token;
} }
public getAttendances() : Map<number, Attendee[]> { public getDates() : number[] {
return this.attendances; return Array.from(this.attendances.keys()).sort();
} }
public getDates() : {dates : string[], attendances : number[]}{ public getAttendees(date : number) : Attendee[] {
let dates = []; if(this.attendances.has(date)){
let attendances = []; return this.attendances.get(date)!; //Fuck TS
for (let [date, attendees] of this.attendances.entries()) {
dates.push(DateHelper.formatDate(date))
attendances.push(attendees.length)
} }
return {dates : dates, attendances : attendances}; return [];
} }
public getMaxAttendees(){
let max = 0;
this.attendances.forEach(attendee => {
if(attendee.length > max) max = attendee.length;
})
return max;
}
} }
export interface EventState { export interface EventState {

View File

@@ -6,6 +6,7 @@ import {DateHelper} from "@/helpers/DateHelper.ts";
import {Event} from "@/models/Event.ts"; import {Event} from "@/models/Event.ts";
import ErrorBlock from "@/components/ErrorBlock.vue"; import ErrorBlock from "@/components/ErrorBlock.vue";
import AttendanceGraph from "@/components/AttendanceGraph.vue"; import AttendanceGraph from "@/components/AttendanceGraph.vue";
import Calendar from "@/components/Calendar.vue";
const route = useRoute(); const route = useRoute();
const store = eventCreationStore(); const store = eventCreationStore();
@@ -30,33 +31,39 @@ function extractToken() : string {
</script> </script>
<template> <template>
<div class="container"> <div v-if="!event" class="container error-block">
<div v-if="!event" class="error-block">
<ErrorBlock> <ErrorBlock>
<h1>This event does not exist</h1> <h1>This event does not exist</h1>
</ErrorBlock> </ErrorBlock>
</div> </div>
<div v-else> <div v-else class="container">
<AttendanceGraph :event="event" /> <AttendanceGraph :event="event" />
<Calendar/>
</div> </div>
</div>
</template> </template>
<style scoped> <style scoped>
.error-block{ .error-block{
width: 100%; width: 100%;
} }
@media screen and (min-width: 1001px) {
.attendance-graph .attendance-graph
{ {
width: 50%;
height: 50%;
}
} }
@media screen and (max-width: 1000px) {
.attendance-graph
{
width: 500px;
height: 500px;
}
}
</style> </style>

View File

@@ -32,7 +32,6 @@ const token = ref("");
min-width: 300px; min-width: 300px;
} }
.actions-group { .actions-group {
min-height: 250px; min-height: 250px;
} }