Search code examples
javascriptvuejs2chart.js

Chart.js 3.9 and Vue: Unable to handle onClick event


Usually, StackOverflow is my last resort cause I don't enjoy being annoyed and I assume neither do you, but I'm desperate.

I want to capture a click on a graph Bar but I don't see a trace in the logs that the click event has been thrown. I was using Chart.js 2.8.9 but upgraded to 3.9 thinking it could be a bug.

This is my vue file where I load the Graphics:

<template>
    <div>
        <h4>Gráficas de miembros activos</h4>
        <div>
            <!-- Pasamos chartData y el evento bar-clicked -->
            <bar-chart :chart-data="datos" :opciones="opciones" @bar-clicked="handleBarClick" />
        </div>
    </div>
</template>

<script>
    import BarChart from "../components/BarChart";
    import api from "@/api";

    export default {
        name: "Graficas",
        components: { BarChart },
        data() {
            return {
                datos: {},   // Datos del gráfico
                opciones: {} // Opciones del gráfico
            };
        },
        methods: {
            graficar: function (sector_id) {
                api.graficas(sector_id).then((r) => {
                    this.datos = r.data; 

                    // Configuración de colores y bordes de las barras
                    this.datos.datasets[0].backgroundColor = [
                        'rgba(255, 99, 132, 0.2)',
                        'rgba(255, 159, 64, 0.2)',
                        'rgba(255, 205, 86, 0.2)',
                        'rgba(75, 192, 192, 0.2)',
                        'rgba(54, 162, 235, 0.2)',
                        'rgba(153, 102, 255, 0.2)',
                        'rgba(201, 203, 207, 0.2)'
                    ];
                    this.datos.datasets[0].borderColor = [
                        'rgb(255, 99, 132)',
                        'rgb(255, 159, 64)',
                        'rgb(255, 205, 86)',
                        'rgb(75, 192, 192)',
                        'rgb(54, 162, 235)',
                        'rgb(153, 102, 255)',
                        'rgb(201, 203, 207)'
                    ];
                    this.datos.datasets[0].borderWidth = 1;

                    // Opciones del gráfico
                    this.opciones = {
                        scales: {
                            y: {
                                beginAtZero: true
                            }
                        },
                        responsive: true,
                        maintainAspectRatio: false,
                        height: 300
                    };
                })
                .catch(error => {
                    console.log(error);
                });
            },
            handleBarClick({ data, dataIndex, datasetIndex }) {
                // Este método captura el evento bar-clicked y muestra la información del clic
                console.log(`Clicked bar at index ${dataIndex} in dataset ${datasetIndex}:`, data);
            }
        },
        mounted() {
            const sector_id = this.$route.params.id ?? 0;
            this.graficar(sector_id);
        },
    };
</script>

And this is the class we created to extend Chart.js:

import { Chart, registerables } from 'chart.js';

Chart.register(...registerables); // Registro de todos los componentes necesarios

export default {
  props: {
    chartData: {
      type: Object,
      required: true
    },
    opciones: {
      type: Object,
      default: () => ({})
    }
  },
  mounted() {
    const canvas = this.$refs.canvas;

    // Verificamos que el canvas existe y tiene contexto 2D
    if (!canvas || !canvas.getContext) {
      console.error("No se pudo obtener el contexto del canvas.");
      return;
    }

    const ctx = canvas.getContext('2d');

    // Configuración de las opciones de la gráfica
    this.opciones.scales = {
      y: {
        beginAtZero: true
      }
    };
    this.opciones.plugins = {
      datalabels: {
        color: "black",
        textAlign: "center",
        anchor: 'start',
        font: {
          weight: "bold",
          size: 14,
        }
      }
    };
    this.opciones.responsive = true;
    this.opciones.maintainAspectRatio = false;
    this.opciones.hover = {
      mode: 'nearest',
      intersect: true
    };
    this.opciones.interaction = {
      mode: 'index',
      intersect: false
    };

    // Establecemos una altura fija para el canvas a través de CSS
    canvas.style.height = '400px';
    canvas.style.width = '100%'; 

    // Instancia del gráfico
    this.chartInstance = new Chart(ctx, {
      type: 'bar',
      data: this.chartData,
      options: {
        ...this.opciones,
        onClick: (event, elements) => {
          console.log('onClick event triggered');
          if (elements.length > 0) {
            const element = elements[0];
            const dataIndex = element.index;
            const datasetIndex = element.datasetIndex;
            console.log('Data point clicked:', dataIndex, datasetIndex);
            const data = this.chartData.datasets[datasetIndex].data[dataIndex];
            this.$emit('bar-clicked', { data, dataIndex, datasetIndex });
          } else {
            console.log('No elements clicked.');
          }
        }
      }
    });

  },
  watch: {
    chartData(newData) {
      if (this.chartInstance) {
        this.chartInstance.data = newData;
        this.chartInstance.update();
      }
    },
    opciones(newOptions) {
      if (this.chartInstance) {
        this.chartInstance.options = newOptions;
        this.chartInstance.update();
      }
    }
  },
  beforeDestroy() {
    if (this.chartInstance) {
      this.chartInstance.destroy();
    }
  },
  render(h) {
    return h('canvas', { ref: 'canvas' });
  }
};

The JSON returned by the backend is simple, so the error shouldn't be there:

    {
  "datasets": [
    {
      "data": [
        15,
        5,
        0,
        0,
        0
      ],
      "label": "Sector San Felipe De Jesus"
    }
  ],
  "labels": [
    "Matrimonios",
    "MaRes",
    "J\u00f3venes",
    "Adolescentes",
    "Asistentes Eclesiales"
  ]
}

The Graphic is shown but I don't see a trace in the logs for the click. Any ideas? Graph is shown but nothing in the logs


Solution

  • Just in case it happens to someone else. There were two blockers:

    1. There was a plugin installed that was not compatible: chartjs-plugin-datalabels. Must uninstall it: npm uninstall chartjs-plugin-datalabels and re compile: npm install
    2. The click was not placed correctly. According to this post, it must be placed inside the canvas instead of options:
        canvas.onclick = (evt) => {
          const res = chart.getElementsAtEventForMode(
            evt,
            'nearest',
            { intersect: true },
            true
          );
          // If didn't click on a bar, `res` will be an empty array
          if (res.length === 0) {
            return;
          }
          // Alerts "You clicked on A" if you click the "A" chart
          alert('You clicked on ' + chart.data.labels[res[0].index]);
        };