graph.html 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. <form action="/" method="post">
  2. <input type="hidden" name="page" value="menu">
  3. <button class="btn btn-primary mb-4">
  4. <i class="fas fa-arrow-left"></i> Regresar
  5. </button>
  6. </form>
  7. <main class="container" @vue:mounted="mounted">
  8. <form class="mb-4">
  9. <div class="row mb-3 form-box">
  10. <label for="alumno.grupo" class="col-sm-1 col-form-label form-group barra">Grupo</label>
  11. <div class="col-sm-10">
  12. <select name="alumno.grupo" id="alumno.grupo" class="form-control col-form-label"
  13. v-model="filter.grupo">
  14. <option :value="null">Todos los grupos</option>
  15. <option v-for="alumno in alumnos.filter(alumno => alumno.grupo)" :value="alumno.grupo">
  16. {{ alumno.grupo }}
  17. </option>
  18. </select>
  19. </div>
  20. </div>
  21. <div class="row mb-3 form-box">
  22. <label for="alumno" class="col-sm-1 col-form-label form-group barra">Alumno</label>
  23. <div class="col-sm-10">
  24. <input type="item.type" name="item.name" list="item.name" id="alumno"
  25. class="form-control col-form-label" @input="fetchDatos" v-model="filter.username">
  26. <datalist id="item.name">
  27. <option v-for="alumno in alumnos_por_grupo" :value="alumno.username">
  28. {{ alumno.nombre }}
  29. </option>
  30. </datalist>
  31. </div>
  32. </div>
  33. <button type="reset" class="btn btn-danger btn-sm">
  34. <i class="fas fa-eraser"></i>
  35. </button>
  36. </form>
  37. <section v-if="datos != null">
  38. <h2>Gráficos</h2>
  39. <div class="row">
  40. <div class="col-md-6">
  41. <canvas id="calificaciones" width="400" height="400" @vue:mounted="createGraph($el,
  42. datos.calificaciones.map(calificacion => calificacion.course_name),
  43. datos.calificaciones.map(calificacion => calificacion.calificación_final),
  44. 'bar', 'Calificaciones')">
  45. </div>
  46. <div class="col-md-6">
  47. <canvas id="snapshot" width="400" height="400"
  48. @vue:mounted="createGraph($el, datos.timeline.map(snapshot => snapshot.created_at), datos.timeline.map(snapshot => snapshot.promedio_calificacion_final), 'line', 'Snapshot')">
  49. </div>
  50. </div>
  51. </section>
  52. </main>
  53. <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  54. <script>
  55. const filter = PetiteVue.reactive({
  56. grupo: null,
  57. username: null,
  58. });
  59. const graph = PetiteVue.reactive({
  60. calificaciones: null,
  61. snapshot: null,
  62. })
  63. PetiteVue.createApp({
  64. store,
  65. filter,
  66. graph,
  67. alumnos: [], // schema: { grupo: string, alumnos: Array<{ nombre: string, username: string }> }
  68. get alumnos_por_grupo() { // returns Array<{ nombre: string, username: string }>
  69. if (this.filter.grupo)
  70. return this.alumnos.find(alumno => alumno.grupo === this.filter.grupo).alumnos;
  71. return this.alumnos.flatMap(alumno => alumno.alumnos);
  72. },
  73. datos: null,
  74. async mounted() {
  75. store.loading = true;
  76. const response = await fetch('fetch/alumnos.php');
  77. const data = await response.json();
  78. this.alumnos = data;
  79. store.loading = false;
  80. },
  81. createGraph($el, labels, data, type, title) {
  82. const backgroundColor = [
  83. 'rgba(255, 99, 132, 0.2)',
  84. 'rgba(54, 162, 235, 0.2)',
  85. 'rgba(255, 206, 86, 0.2)',
  86. 'rgba(75, 192, 192, 0.2)',
  87. 'rgba(153, 102, 255, 0.2)',
  88. 'rgba(255, 159, 64, 0.2)',
  89. 'rgba(255, 99, 132, 0.2)',
  90. 'rgba(54, 162, 235, 0.2)',
  91. 'rgba(255, 206, 86, 0.2)',
  92. 'rgba(75, 192, 192, 0.2)',
  93. ];
  94. const borderColor = [
  95. 'rgba(255, 99, 132, 1)',
  96. 'rgba(54, 162, 235, 1)',
  97. 'rgba(255, 206, 86, 1)',
  98. 'rgba(75, 192, 192, 1)',
  99. 'rgba(153, 102, 255, 1)',
  100. 'rgba(255, 159, 64, 1)',
  101. 'rgba(255, 99, 132, 1)',
  102. 'rgba(54, 162, 235, 1)',
  103. 'rgba(255, 206, 86, 1)',
  104. 'rgba(75, 192, 192, 1)',
  105. ];
  106. if (this.datos == null) return;
  107. const ctx1 = $el.getContext('2d');
  108. const chart1 = new Chart(ctx1, {
  109. type: type,
  110. data: {
  111. labels: labels,
  112. datasets: [{
  113. label: title,
  114. data: data,
  115. backgroundColor: backgroundColor.slice(0, data.length),
  116. borderColor: borderColor.slice(0, data.length),
  117. borderWidth: 1
  118. }]
  119. },
  120. options: {
  121. scales: {
  122. y: {
  123. beginAtZero: true
  124. }
  125. }
  126. }
  127. });
  128. },
  129. async fetchDatos() {
  130. if (this.filter.username == null) return;
  131. if (this.alumnos_por_grupo.find(alumno => alumno.username === this.filter.username) == null) return;
  132. store.loading = true;
  133. // params POST (formadata [username])
  134. const params = new FormData();
  135. params.append('username', this.filter.username);
  136. const response = await fetch('fetch/calificaciones.php', {
  137. method: 'POST',
  138. body: params,
  139. });
  140. const data = await response.json();
  141. this.datos = data;
  142. store.loading = false;
  143. /* this.createGraph(); */
  144. }
  145. }).mount()
  146. </script>