Add graph
This commit is contained in:
@@ -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);
|
||||||
|
let bar = addBar(container);
|
||||||
|
setBarSize(bar, graph.getRatio(date));
|
||||||
|
setBarTooltip(bar, graph.getAttendees(date).length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
new Chart(ctx as ChartItem, {
|
function addBar(container : HTMLElement) : HTMLElement {
|
||||||
type: 'doughnut',
|
let bar = document.createElement("div");
|
||||||
data : {
|
bar.classList.add("bar");
|
||||||
labels: data.dates,
|
container.appendChild(bar);
|
||||||
datasets: [{
|
return bar;
|
||||||
label: 'Attendances',
|
}
|
||||||
data: data.attendances,
|
|
||||||
|
|
||||||
hoverOffset: 4
|
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>
|
||||||
|
|||||||
28
front/src/models/AttendanceGraph.ts
Normal file
28
front/src/models/AttendanceGraph.ts
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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%;
|
||||||
}
|
}
|
||||||
.attendance-graph
|
|
||||||
{
|
@media screen and (min-width: 1001px) {
|
||||||
|
.attendance-graph
|
||||||
|
{
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1000px) {
|
||||||
|
.attendance-graph
|
||||||
|
{
|
||||||
|
width: 500px;
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ const token = ref("");
|
|||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.actions-group {
|
.actions-group {
|
||||||
min-height: 250px;
|
min-height: 250px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user