RocketChat/Rocket.Chat

View on GitHub
apps/meteor/app/livechat/client/lib/chartHandler.ts

Summary

Maintainability
A
1 hr
Test Coverage
import type { ChartItem, Chart as ChartType, ChartConfiguration } from 'chart.js';

import { t } from '../../../utils/lib/i18n';

type LineChartConfigOptions = Partial<{
    legends: boolean;
    anim: boolean;
    displayColors: boolean;
    tooltipCallbacks: any;
}>;

const lineChartConfiguration = ({
    legends = false,
    anim = false,
    tooltipCallbacks = {},
}: LineChartConfigOptions): Partial<ChartConfiguration<'line', number, string>['options']> => {
    const config: ChartConfiguration<'line', number, string>['options'] = {
        layout: {
            padding: {
                top: 10,
                bottom: 0,
            },
        },
        legend: {
            display: false,
        },
        plugins: {
            tooltip: {
                usePointStyle: true,
                enabled: true,
                mode: 'point',
                yAlign: 'bottom',
                displayColors: true,
                ...tooltipCallbacks,
            },
        },
        scales: {
            xAxis: {
                title: {
                    display: false,
                },
                grid: {
                    display: true,
                    color: 'rgba(0, 0, 0, 0.03)',
                },
            },
            yAxis: {
                title: {
                    display: false,
                },
                grid: {
                    display: true,
                    color: 'rgba(0, 0, 0, 0.03)',
                },
            },
        },
        hover: {
            intersect: false, // duration of animations when hovering an item
            mode: 'index',
        },
        responsive: true,
        maintainAspectRatio: false,
        ...(!anim ? { animation: { duration: 0 } } : {}),
        ...(legends ? { legend: { display: true, labels: { boxWidth: 20, fontSize: 8 } } } : {}),
    };

    return config;
};

const doughnutChartConfiguration = (
    title: string,
    tooltipCallbacks = {},
): Partial<ChartConfiguration<'doughnut', number, string>['options']> => ({
    layout: {
        padding: {
            top: 0,
            bottom: 0,
        },
    },
    plugins: {
        legend: {
            display: true,
            position: 'right',
            labels: {
                boxWidth: 20,
            },
        },
        title: {
            display: true,
            text: title,
        },
        tooltip: {
            enabled: true,
            mode: 'point',
            displayColors: true, // hide color box
            ...tooltipCallbacks,
        },
    },
    // animation: {
    //     duration: 0 // general animation time
    // },
    hover: {
        intersect: true, // duration of animations when hovering an item
    },
    responsive: true,
    maintainAspectRatio: false,
});

type ChartDataSet = {
    label: string;
    data: number;
    backgroundColor: string;
    borderColor: string;
    borderWidth: number;
    fill: boolean;
};

/**
 *
 * @param {Object} chart - chart element
 * @param {Object} chartContext - Context of chart
 * @param {Array(String)} chartLabel
 * @param {Array(String)} dataLabels
 * @param {Array(Array(Double))} dataPoints
 */
export const drawLineChart = async (
    chart: HTMLCanvasElement,
    chartContext: { destroy: () => void } | undefined,
    chartLabels: string[],
    dataLabels: string[],
    dataSets: number[],
    options: LineChartConfigOptions = {},
): Promise<ChartType<'line', number, string> | void> => {
    if (!chart) {
        console.error('No chart element');
        return;
    }
    if (chartContext) {
        chartContext.destroy();
    }
    const colors = ['#2de0a5', '#ffd21f', '#f5455c', '#cbced1'];

    const datasets: ChartDataSet[] = [];

    chartLabels.forEach((chartLabel: string, index: number) => {
        datasets.push({
            label: t(chartLabel), // chart label
            data: dataSets[index], // data points corresponding to data labels, x-axis points
            backgroundColor: colors[index],
            borderColor: colors[index],
            borderWidth: 3,
            fill: false,
        });
    });
    const chartjs = await import('chart.js/auto');
    const Chart = chartjs.default;
    return new Chart(chart, {
        type: 'line',
        data: {
            labels: dataLabels, // data labels, y-axis points
            datasets,
        },
        options: lineChartConfiguration(options),
    });
};

/**
 *
 * @param {Object} chart - chart element
 * @param {Object} chartContext - Context of chart
 * @param {Array(String)} dataLabels
 * @param {Array(Double)} dataPoints
 */
export const drawDoughnutChart = async (
    chart: ChartItem,
    title: string,
    chartContext: { destroy: () => void } | undefined,
    dataLabels: string[],
    dataPoints: number[],
): Promise<ChartType<'doughnut', number[], string> | void> => {
    if (!chart) {
        console.error('No chart element');
        return;
    }
    if (chartContext) {
        chartContext.destroy();
    }
    const chartjs = await import('chart.js/auto');
    const Chart = chartjs.default;
    return new Chart(chart, {
        type: 'doughnut',
        data: {
            labels: dataLabels, // data labels, y-axis points
            datasets: [
                {
                    data: dataPoints, // data points corresponding to data labels, x-axis points
                    backgroundColor: ['#2de0a5', '#cbced1', '#f5455c', '#ffd21f'],
                    borderWidth: 0,
                },
            ],
        },
        options: doughnutChartConfiguration(title),
    });
};

/**
 * Update chart
 * @param  {Object} chart [Chart context]
 * @param  {String} label [chart label]
 * @param  {Array(Double)} data  [updated data]
 */
export const updateChart = async (c: ChartType, label: string, data: { [x: string]: number }): Promise<void> => {
    const chart = await c;
    if (chart.data?.labels?.indexOf(label) === -1) {
        // insert data
        chart.data.labels.push(label);
        chart.data.datasets.forEach((dataset: { data: any[] }, idx: string | number) => {
            dataset.data.push(data[idx]);
        });
    } else {
        // update data
        const index = chart.data?.labels?.indexOf(label);
        if (typeof index === 'undefined') {
            return;
        }

        chart.data.datasets.forEach((dataset: { data: { [x: string]: any } }, idx: string | number) => {
            dataset.data[index] = data[idx];
        });
    }

    chart.update();
};