TransWikia.com

Como mostrar datasets correctamente en una gráfica de barras apiladas usando Chart.js

Stack Overflow en español Asked on November 20, 2021

Estoy desarrollando un pequeño dashboard en ASP.NET, con la ayuda de C# y estoy presentando un problema al generar una gráfica de barras horizontales apiladas (stacked horizontal bar) usando Chart.js.

Tengo los datos de unos técnicos a los cuales se les asignaron cierta cantidad de tickets, que según la imagen corresponden al eje Y de la gráfica. A, B, C y D representan a cada técnico y los datasets del 1 al 4 representan el estado en el que se encuentra cada ticket. Lo que quiero obtener en la gráfica es la cantidad de tickets que tiene cada técnico teniendo en cuenta su estado. Adjunto una imagen de ejemplo de lo que quiero realizar:

introducir la descripción de la imagen aquí

Error:

Estoy realizando la gráfica de barras horizontal esperada tal como lo indico en el ejemplo anterior pero no he logrado que se vean los datasets y sus colores correctamente.

Lo que he logrado hasta ahora es ver los técnicos con la cantidad de tickets que tiene cada uno, pero el dataset que representa el estado en la parte inferior de la gráfica se repite, ademas de ASIGNADO tengo otros estados y cada dataset, o sea, cada estado, tiene que ir representado por un color:

introducir la descripción de la imagen aquí

Para ser mas exactos aquí agrego el ejemplo de Stacked bar que se encuentra en la documentación de Chart.js

Los datos se estructuran de la siguiente manera en la tabla desde donde estoy sacando la información que quiero mostrar, donde, TK_HD_TICKETS_ID son los tickets que están registrados y de donde quiero sacar la cantidad, en TK_CT_STATUS_ID se almacena el estado del ticket y en TK_BT_EMPLOYEES_ID es el técnico que tiene ese ticket

La siguiente es la función Javascript con la que voy cargar los datos en la gráfica:

function loadEmployeesChart() {
    document.getElementById("chart-employee").remove();
    let auxCanvasEmployee = document.createElement('canvas');
    auxCanvasEmployee.setAttribute('id', 'chart-employee');
    auxCanvasEmployee.setAttribute('style', 'width: 720px; height: 600px');

    document
        .querySelector('#chartEmployeeContainer')
        .appendChild(auxCanvasEmployee);

    var canvas = document.getElementById("chart-employee");
    var ctx = canvas.getContext("2d");
    var dataEmployee;
    var myNewChart;

    $.ajax({
        url: document.getElementById("employeeChart").value,
        type: "POST",
        dataType: "json",
        data: {
            refreshType: document.getElementById("dataOption").value
        },
        success: function (data) {
            var dataChart = [];
            var label = [];
            var datalabels = [];
            var stacks = []

            for (let idx in data.DashboardTicketList) {
                if (data.DashboardTicketList.hasOwnProperty(idx)) {
                    dataChart.push(data.DashboardTicketList[idx].TicketsCount);
                    label.push(data.DashboardTicketList[idx].TicketsAsignedTo);
                    datalabels.push(data.DashboardTicketList[idx].TicketsClasificationType);                
                }
            }

            var count = false;
            for (let idx in dataChart) {
                if (dataChart[idx] > 0) {
                    count = true;
                    break;
                }
            }

            if (count) {
                document.getElementById('noDataEmployee').style.display = 'none';
            } else {
                document.getElementById('noDataEmployee').style.display = 'block';
            }

            dataEmployee = { 
                labels: label,
                datasets: [{
                    label: datalabels,
                    data: dataChart,
                }],    
            };

            myNewChart = new Chart(ctx, {
                type: 'horizontalBar',           
                data: dataEmployee,
                options: {
                    maintainAspectRatio: false,
                    
                    scales: {
                        xAxes: [{                            
                            stacked: true // this should be set to make the bars stacked
                        }],
                        yAxes: [{
                            stacked: true // this also..
                        }]
                    },
                    legend: {
                        position: 'bottom',
                        padding: 5,
                        labels:
                        {
                            pointStyle: 'circle',
                            usePointStyle: true
                        }
                    }
                },
                
            });
        },
        error: function () { }
    });   
}

2 Answers

El problema que tienes está en la transformación que haces de los datos que te llegan de la consulta y en la implementación de los mismos. Prácticamente estás creando tantos labels o datasets como resultados obtienes de la consulta, y esto deberías hacerlo agrupando los resultados, de lo contrario tendrás muchos datos repetidos. Lo mejor es que tus datos ya vinieran agrupados del servidor, pero tomando como fuente los datos que te llegan en este momento, intentaré describirte paso por paso una posible solución (aunque hay muchas posibles).

Imagina que la consulta te devuelve un número x de filas, tomemos para este ejemplo las siguientes:

[
    {TicketsCount: 44, TicketsAsignedTo: "A", TicketsClasificationType: "Dataset 1"},
    {TicketsCount: 55, TicketsAsignedTo: "B", TicketsClasificationType: "Dataset 1"},
    {TicketsCount: 41, TicketsAsignedTo: "C", TicketsClasificationType: "Dataset 1"},
    {TicketsCount: 37, TicketsAsignedTo: "D", TicketsClasificationType: "Dataset 1"},
    {TicketsCount: 53, TicketsAsignedTo: "A", TicketsClasificationType: "Dataset 2"},
    {TicketsCount: 32, TicketsAsignedTo: "B", TicketsClasificationType: "Dataset 2"},
    {TicketsCount: 33, TicketsAsignedTo: "C", TicketsClasificationType: "Dataset 2"},
    {TicketsCount: 52, TicketsAsignedTo: "D", TicketsClasificationType: "Dataset 2"},
    {TicketsCount: 12, TicketsAsignedTo: "A", TicketsClasificationType: "Dataset 3"},
    {TicketsCount: 17, TicketsAsignedTo: "B", TicketsClasificationType: "Dataset 3"},
    {TicketsCount: 11, TicketsAsignedTo: "C", TicketsClasificationType: "Dataset 3"},
    {TicketsCount: 9,  TicketsAsignedTo: "D", TicketsClasificationType: "Dataset 3"},
    {TicketsCount: 9,  TicketsAsignedTo: "A", TicketsClasificationType: "Dataset 4"},
    {TicketsCount: 7,  TicketsAsignedTo: "B", TicketsClasificationType: "Dataset 4"},
    {TicketsCount: 5,  TicketsAsignedTo: "C", TicketsClasificationType: "Dataset 4"},
    {TicketsCount: 8,  TicketsAsignedTo: "D", TicketsClasificationType: "Dataset 4"},
    {TicketsCount: 40, TicketsAsignedTo: "E", TicketsClasificationType: "Dataset 1"}
]

Primero que todo, se deben crear los labels que necesita el gráfico (estos deben ser únicos, no debe haber elementos repetidos):

const labels = Object.keys(
    consulta.reduce((obj, label) => (obj[label.TicketsAsignedTo] = true, obj), {})
);

Esto te debería devolver el siguiente array:

["A", "B", "C", "D", "E"]

Después se necesitan filtrar tus datos para agruparlos, para ello se puede generar un objeto compuesto por propiedades de tipo TicketsClasificationType y como valor de dichas propiedades, un objeto conformado por propiedades de tipo TicketsAsignedTo y valores TicketsCount:

const datasetsObject = consulta.reduce((obj, item) => {
    obj[item.TicketsClasificationType] = obj[item.TicketsClasificationType] || {};
    obj[item.TicketsClasificationType][item.TicketsAsignedTo] = item.TicketsCount;
    return obj;
}, {});

Esto devolverá un objeto como el siguiente:

{
    "Dataset 1": {A: 44, B: 55, C: 41, D: 37, E: 40},
    "Dataset 2": {A: 53, B: 32, C: 33, D: 52},
    "Dataset 3": {A: 12, B: 17, C: 11, D: 9},
    "Dataset 4": {A: 9,  B: 7,  C: 5,  D: 8}
}

Al tener los datos de la consulta ya agrupados, se podría generar el array de datasets que necesitas:

const datasets = Object.keys(datasetsObject).map(name => ({
    label: name,
    data: labels.map(l => datasetsObject[name][l] || 0)
}));

Aquí te dejo un snippet de ejemplo para que puedas ver el código funcionando:

const consulta = [
    {TicketsCount: 44, TicketsAsignedTo: "A", TicketsClasificationType: "Dataset 1"},
    {TicketsCount: 55, TicketsAsignedTo: "B", TicketsClasificationType: "Dataset 1"},
    {TicketsCount: 41, TicketsAsignedTo: "C", TicketsClasificationType: "Dataset 1"},
    {TicketsCount: 37, TicketsAsignedTo: "D", TicketsClasificationType: "Dataset 1"},
    {TicketsCount: 53, TicketsAsignedTo: "A", TicketsClasificationType: "Dataset 2"},
    {TicketsCount: 32, TicketsAsignedTo: "B", TicketsClasificationType: "Dataset 2"},
    {TicketsCount: 33, TicketsAsignedTo: "C", TicketsClasificationType: "Dataset 2"},
    {TicketsCount: 52, TicketsAsignedTo: "D", TicketsClasificationType: "Dataset 2"},
    {TicketsCount: 12, TicketsAsignedTo: "A", TicketsClasificationType: "Dataset 3"},
    {TicketsCount: 17, TicketsAsignedTo: "B", TicketsClasificationType: "Dataset 3"},
    {TicketsCount: 11, TicketsAsignedTo: "C", TicketsClasificationType: "Dataset 3"},
    {TicketsCount: 9,  TicketsAsignedTo: "D", TicketsClasificationType: "Dataset 3"},
    {TicketsCount: 9,  TicketsAsignedTo: "A", TicketsClasificationType: "Dataset 4"},
    {TicketsCount: 7,  TicketsAsignedTo: "B", TicketsClasificationType: "Dataset 4"},
    {TicketsCount: 5,  TicketsAsignedTo: "C", TicketsClasificationType: "Dataset 4"},
    {TicketsCount: 8,  TicketsAsignedTo: "D", TicketsClasificationType: "Dataset 4"},
    {TicketsCount: 40, TicketsAsignedTo: "E", TicketsClasificationType: "Dataset 1"}
];

const labels = Object.keys(
    consulta.reduce((obj, label) => (obj[label.TicketsAsignedTo] = true, obj), {})
);

const colors = {
    "Dataset 1": "#22aa99",
    "Dataset 2": "#994499",
    "Dataset 3": "#316395",
    "Dataset 4": "#b82e2e"
};

const datasetsObject = consulta.reduce((obj, item) => {
    obj[item.TicketsClasificationType] = obj[item.TicketsClasificationType] || {};
    obj[item.TicketsClasificationType][item.TicketsAsignedTo] = item.TicketsCount;
    return obj;
}, {});

const datasets = Object.keys(datasetsObject).map(name => ({
    label: name,
    backgroundColor: colors[name],
    data: labels.map(l => datasetsObject[name][l] || 0)
}));

const context = document.getElementById("barChart").getContext("2d");
const myBarChart = new Chart(context, {
    type: "horizontalBar",
    data: { labels, datasets },
    options: {
        scales: {
            xAxes: [{ stacked: true }],
            yAxes: [{ stacked: true }]
        }
    }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.0/Chart.bundle.min.js"></script>

<canvas id="barChart"></canvas>

Answered by ElChiniNet on November 20, 2021

En este fiddle hay un ejemplo adaptado de esta pregunta de SO, que te puede servir de base.

Si te fijas en datasets, cada elemento del mismo grupo repite data. La diferencia es que cambia el stack, que sería el subgrupo. Si pusieras los datos originales se podría dar mas detalle en cómo preparar los datasets.

datasets: [
    {
        label: "Apples",
        backgroundColor: "rgba(99,255,132,0.2)",
        data: [20, 10, 30],
        stack: 1
    },
    {
        label: "Apples",
        backgroundColor: "rgba(99,255,132,0.2)",
        data: [20, 10, 30],
        stack: 2
    },
    {
        label: "Apples",
        backgroundColor: "rgba(99,255,132,0.2)",
        data: [20, 10, 30],
        stack: 3
    }
]

Answered by Emeeus on November 20, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP