chartjs-plugin-stacked100 icon indicating copy to clipboard operation
chartjs-plugin-stacked100 copied to clipboard

Doesn't seem to work with ng2-charts

Open jyotidhiman0610 opened this issue 7 years ago • 3 comments

I installed the plugin as specified in the docs, I am using ng2-charts with following options: public barChartOptions = { plugins: { stacked100: { enable: true }, }, legend: { display: true, position: 'bottom', }, scales: { xAxes: [{ display: true, stacked: true }], yAxes: [{ display: false, stacked: true }], }, };

No percentages are shown for me. The chart still displays value options.

jyotidhiman0610 avatar Aug 14 '18 07:08 jyotidhiman0610

hi @jyotidhiman0610 did you find any solution i am also using ng2-charts but my charts didn't stacked at 100 %

ankitp047 avatar Jan 06 '19 19:01 ankitp047

ng2-charts has some additional fiddling to make plugin registration work correctly, once it is hooked in correctly the stacked charts do work.

You'll want to take a look at this issue discussion: https://github.com/valor-software/ng2-charts/issues/752#issuecomment-439060266

srmccray avatar Apr 04 '19 17:04 srmccray

This is not a solution but you found a quick solution for use this lib with angular :

  • Create new file stacked-100.lib.ts
import { Chart } from 'chart.js';

/**
 * Chart.JS and Angular can be incompatible.
 * This function is inspired of lib : https://github.com/y-takey/chartjs-plugin-stacked100
 *
 * Add custom plugins for stacked bar with 100% Y axe.
 */
export const chartJsRegistererStacked100 = () => {
    const isObject = function (obj) {
        const type = typeof obj;
        return type === 'object' && !!obj;
    };

    const dataValue = function (dataPoint, isHorizontal) {
        if (isObject(dataPoint)) {
            return isHorizontal ? dataPoint.x : dataPoint.y;
        }

        return dataPoint;
    };

    const cloneArray = function (srcAry) {
        const dstAry = [];
        const length = srcAry.length;

        for (let i = 0; i < length; i++) {
            dstAry.push(srcAry[i]);
        }
        return dstAry;
    };

    const setOriginalData = function (data) {
        data.originalData = data.datasets.map(function (dataset) {
            return cloneArray(dataset.data);
        });
    };

    // set calculated rate (xx%) to data.calculatedData
    const calculateRate = function (data, isHorizontal, precision) {
        const visibles = data.datasets.map(function (dataset) {
            if (!dataset._meta) {
                return true;
            }

            for (const i in dataset._meta) {
                return !dataset._meta[i].hidden;
            }
        });

        let datasetDataLength = 0;
        if (data && data.datasets && data.datasets[0] && data.datasets[0].data) {
            datasetDataLength = data.datasets[0].data.length;
        }
        const totals = Array.apply(null, new Array(datasetDataLength)).map(function (el, i) {
            return data.datasets.reduce(function (sum, dataset, j) {
                const key = dataset.stack;
                if (!sum[key]) {
                    sum[key] = 0;
                }
                sum[key] += Math.abs(dataValue(dataset.data[i], isHorizontal)) * visibles[j];

                return sum;
            }, {});
        });

        data.calculatedData = data.datasets.map(function (dataset, i) {
            // tslint:disable-next-line:no-shadowed-variable
            return dataset.data.map(function (val, i) {
                const total = totals[i][dataset.stack];
                const dv = dataValue(val, isHorizontal);
                return dv && total ? round(dv / total, precision) : 0;
            });
        });
    };

    const getPrecision = function (pluginOptions) {
        // return the (validated) configured precision from pluginOptions or default 1
        const defaultPrecision = 1;
        if (!pluginOptions.hasOwnProperty('precision')) {
            return defaultPrecision;
        }
        if (!pluginOptions.precision) {
            return defaultPrecision;
        }
        const optionsPrecision = Math.floor(pluginOptions.precision);
        if (isNaN(optionsPrecision)) {
            return defaultPrecision;
        }
        if (optionsPrecision < 0 || optionsPrecision > 16) {
            return defaultPrecision;
        }
        return optionsPrecision;
    };

    const round = function (value, precision) {
        const multiplicator = Math.pow(10, precision);
        return Math.round(value * 100 * multiplicator) / multiplicator;
    };

    const tooltipLabel = function (isHorizontal) {
        return function (tooltipItem, data) {
            const datasetIndex = tooltipItem.datasetIndex;
            const index = tooltipItem.index;
            const originalValue = data.originalData[datasetIndex][index];
            const rateValue = data.calculatedData[datasetIndex][index];

            return '' + rateValue + '% (' + dataValue(originalValue, isHorizontal) + ')';
        };
    };

    const reflectData = function (srcData, datasets) {
        if (!srcData) {
            return;
        }

        srcData.forEach(function (data, i) {
            datasets[i].data = data;
        });
    };

    // tslint:disable-next-line:no-shadowed-variable
    const isHorizontalChart = function (chartInstance) {
        return chartInstance.config.type === 'horizontalBar';
    };

    // tslint:disable-next-line:no-shadowed-variable
    const Stacked100Plugin = {
        id: 'stacked100',

        // tslint:disable-next-line:no-shadowed-variable
        beforeInit: function (chartInstance) {
            if (!chartInstance.options.stacked100 || !chartInstance.options.stacked100.enable) {
                return;
            }

            const xAxes = chartInstance.options.scales.xAxes;
            const yAxes = chartInstance.options.scales.yAxes;
            const isVertical = chartInstance.config.type === 'bar' || chartInstance.config.type === 'line';

            [xAxes, yAxes].forEach(function (axes) {
                axes.forEach(function (hash) {
                    hash.stacked = true;
                });
            });
            (isVertical ? yAxes : xAxes).forEach(function (hash) {
                if (!hash.ticks.min) {
                    const hasNegative = chartInstance.data.datasets.some(function (dataset) {
                        return dataset.data.some(function (value) {
                            return value < 0;
                        });
                    });
                    hash.ticks.min = hasNegative ? -100 : 0;
                }
                if (!hash.ticks.max) {
                    hash.ticks.max = 100;
                }
            });

            // Replace tooltips
            if (chartInstance.options.stacked100.hasOwnProperty('replaceTooltipLabel') && !chartInstance.options.stacked100.replaceTooltipLabel) {
                return;
            }
            chartInstance.options.tooltips.callbacks.label = tooltipLabel(isHorizontalChart(chartInstance));
        },

        // tslint:disable-next-line:no-shadowed-variable
        beforeDatasetsUpdate: function (chartInstance) {
            if (!chartInstance.options.stacked100 || !chartInstance.options.stacked100.enable) {
                return;
            }
            setOriginalData(chartInstance.data);
            const precision = getPrecision(chartInstance.options.stacked100);
            calculateRate(chartInstance.data, isHorizontalChart(chartInstance), precision);
            reflectData(chartInstance.data.calculatedData, chartInstance.data.datasets);
        },
        // tslint:disable-next-line:no-shadowed-variable
        afterDatasetsUpdate: function (chartInstance) {
            if (!chartInstance.options.stacked100 || !chartInstance.options.stacked100.enable) {
                return;
            }

            reflectData(chartInstance.data.originalData, chartInstance.data.datasets);
        }
    };

    if (!(Chart.pluginService as any).getAll().filter(p => p.id === 'stacked100')[0]) {
        Chart.pluginService.register(Stacked100Plugin);
    }
};

  • and on your chart component :
ngAfterViewInit() {
        const canvas: any = document.getElementById('canvas_' + this.guid);
        const ctx: HTMLCanvasElement = canvas.getContext('2d');

        // bug fix chartjs/angular. registerer stacked100 plugins
        chartJsRegistererStacked100();

        const options: any | Chart.ChartConfiguration = {
            type: this.chart.type,
            data: this.chart.data,
            options: {
                stacked100: {
                    enable: true,
                    replaceTooltipLabel: true,
                }
            },
        };

        const myBar = new Chart(ctx, options);
    }

/!\ chartjs-plugin-stacked100's options is not on "plugins" attribut but on "options" attribut !

gantispam avatar Apr 01 '21 09:04 gantispam