graph.html 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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. <fieldset class="mb-4">
  9. <legend v-if="alumno_y_clave">
  10. {{ alumno_y_clave }}
  11. </legend>
  12. <legend v-else>
  13. Gráficos
  14. </legend>
  15. <form>
  16. <div class="row mb-3 form-box">
  17. <label for="alumno.grupo" class="col-sm-1 col-form-label form-group barra">Grupo</label>
  18. <div class="col-sm-10">
  19. <select name="alumno.grupo" id="alumno.grupo" class="form-control col-form-label"
  20. v-model="filter.grupo">
  21. <option :value="null">Todos los grupos</option>
  22. <option v-for="alumno in alumnos.filter(alumno => alumno.grupo)" :value="alumno.grupo">
  23. {{ alumno.grupo }}
  24. </option>
  25. </select>
  26. </div>
  27. </div>
  28. <div class="row mb-3 form-box">
  29. <label for="alumno" class="col-sm-1 col-form-label form-group barra">Alumno</label>
  30. <div class="col-sm-10">
  31. <input type="item.type" name="item.name" list="item.name" id="alumno"
  32. class="form-control col-form-label" @input="fetchDatos" v-model="filter.username"
  33. placeholder="Nombre o clave del alumno">
  34. <datalist id="item.name">
  35. <option v-for="alumno in alumnos_por_grupo" :value="alumno.username">
  36. {{ alumno.nombre }}
  37. </option>
  38. </datalist>
  39. </div>
  40. </div>
  41. <!-- <button type="reset" class="btn btn-danger btn-sm">
  42. <i class="fas fa-eraser"></i>
  43. </button> -->
  44. </form>
  45. </fieldset>
  46. <section v-if="datos != null">
  47. <h2>Gráficos</h2>
  48. <div class="row d-flex justify-content-center">
  49. <div class="col-md-8">
  50. <div id="snapshot" width="400" height="400" @vue:mounted="createMultiLineGraph($el, datos.snapshots)">
  51. </div>
  52. </div>
  53. <div class="col-md-8">
  54. <div id="calificaciones" width="400" height="400" @vue:mounted="createGraph($el,
  55. datos.calificaciones.map(calificacion => calificacion.course_name),
  56. datos.calificaciones.map(calificacion => calificacion.calificación_final))">
  57. </div>
  58. </div>
  59. </div>
  60. </section>
  61. </main>
  62. <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  63. <script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
  64. <script>
  65. const filter = PetiteVue.reactive({
  66. grupo: null,
  67. username: null,
  68. });
  69. const graph = PetiteVue.reactive({
  70. calificaciones: null,
  71. snapshot: null,
  72. })
  73. PetiteVue.createApp({
  74. store,
  75. filter,
  76. graph,
  77. alumnos: [], // schema: { grupo: string, alumnos: Array<{ nombre: string, username: string }> }
  78. get alumnos_por_grupo() { // returns Array<{ nombre: string, username: string }>
  79. if (this.filter.grupo)
  80. return this.alumnos.find(alumno => alumno.grupo === this.filter.grupo).alumnos;
  81. return this.alumnos.flatMap(alumno => alumno.alumnos);
  82. },
  83. get alumno_y_clave() { // returns string: (username) nombre
  84. if (this.filter.username == null) return null;
  85. const alumno = this.alumnos_por_grupo.find(alumno => alumno.username === this.filter.username);
  86. return alumno ? `(${alumno.username}) ${alumno.nombre}` : null;
  87. },
  88. datos: null,
  89. async mounted() {
  90. store.loading = true;
  91. const response = await fetch('fetch/alumnos.php');
  92. const data = await response.json();
  93. this.alumnos = data;
  94. store.loading = false;
  95. },
  96. createGraph($el, labels, data) {
  97. const colors = [
  98. 'rgba(54, 162, 235, 0.7)', // Blue
  99. 'rgba(255, 99, 132, 0.7)', // Red
  100. 'rgba(255, 159, 64, 0.7)', // Orange
  101. 'rgba(255, 206, 86, 0.7)', // Yellow
  102. 'rgba(75, 192, 192, 0.7)', // Green
  103. 'rgba(153, 102, 255, 0.7)', // Purple
  104. 'rgba(201, 203, 207, 0.7)', // Gray
  105. ];
  106. const series = data.map((datum, index) => ({
  107. name: labels[index], // Assuming each label corresponds to a course
  108. data: datum,
  109. colors: colors[i],
  110. }));
  111. console.log(series);
  112. const options = {
  113. series: [{data, labels, colors}],
  114. chart: {
  115. type: 'bar',
  116. height: 350
  117. },
  118. plotOptions: {
  119. bar: {
  120. borderRadius: 4,
  121. // horizontal: true, // Uncomment or comment based on desired orientation
  122. }
  123. },
  124. dataLabels: {
  125. enabled: false
  126. },
  127. xaxis: {
  128. categories: labels,
  129. },
  130. yaxis: {
  131. min: 0,
  132. max: 1, // Ensures y-axis scales to a max of 1
  133. tickAmount: 5, // Optional: Adjusts the number of ticks on the y-axis
  134. },
  135. };
  136. var chart = new ApexCharts($el, options);
  137. chart.render();
  138. },
  139. createMultiLineGraph($el, snapshots) {
  140. const labels = snapshots.map(snapshot => snapshot.fecha);
  141. const series = snapshots[0].calificaciones.map(calificacion => {
  142. return {
  143. name: calificacion.course_name,
  144. data: snapshots.map(snapshot => snapshot.calificaciones.find(c => c.course_name === calificacion.course_name).calificación_final)
  145. };
  146. });
  147. const options = {
  148. chart: {
  149. type: 'line',
  150. height: 'auto'
  151. },
  152. series: series,
  153. xaxis: {
  154. categories: labels
  155. },
  156. yaxis: {
  157. min: 0,
  158. max: 1, // Assuming scores are normalized between 0 and 1
  159. tickAmount: 5, // Adjust tick amount as needed
  160. labels: {
  161. formatter: function (val) {
  162. return val.toFixed(2); // Adjust the number of decimal places as needed
  163. }
  164. }
  165. },
  166. title: {
  167. text: 'Calificaciones por Materia a lo largo del Tiempo',
  168. align: 'center'
  169. },
  170. stroke: {
  171. width: 2, // Line thickness
  172. curve: 'smooth' // 'straight' or 'smooth'
  173. },
  174. fill: {
  175. opacity: 1,
  176. },
  177. tooltip: {
  178. y: {
  179. formatter: function (val) {
  180. return val.toFixed(2); // Adjust the number of decimal places as needed
  181. }
  182. }
  183. }
  184. };
  185. var chart = new ApexCharts($el, options);
  186. chart.render();
  187. },
  188. async fetchDatos() {
  189. if (this.filter.username == null) return;
  190. if (this.alumnos_por_grupo.find(alumno => alumno.username === this.filter.username) == null) return;
  191. store.loading = true;
  192. this.datos = null;
  193. // params POST (formadata [username])
  194. const params = new FormData();
  195. params.append('username', this.filter.username);
  196. const response = await fetch('fetch/calificaciones.php', {
  197. method: 'POST',
  198. body: params,
  199. });
  200. const data = await response.json();
  201. this.datos = data;
  202. store.loading = false;
  203. /* this.createGraph(); */
  204. }
  205. }).mount()
  206. </script>