0

chart.js 2 を中心に構築された react-chartjs-2 を使用しています。Chartjs はバージョン 3 を大幅に変更してロールアウトしましたが、react-chartjs はまだ chartjs-3 のラッパーを開発していません。

グループ化された棒グラフで null 値をスキップしたいのですが、それ以外の場合は空白が残ります。null 値をスキップするオプションが chartjs-3 に追加されましたが、chartjs-2 にはありません。chartjs-2 を使用すると、プロットは以下のようになり、null 値のギャップがあります。 ここに画像の説明を入力

したがって、このコミット変更#7849を使用し、chartjs-2.9.2 にも同じ変更を加えています。バーの幅は、null 値の領域を埋めるために増加しましたが、右に移動し、以下のように重なっています。なぜこのように振る舞うのですか?どんな助けでも大歓迎です。 ここに画像の説明を入力

以下のcontoller.bar.jsコードを変更しました

"use strict";

var DatasetController = require("../core/core.datasetController");
var defaults = require("../core/core.defaults");
var elements = require("../elements/index");
var helpers = require("../helpers/index");

var deprecated = helpers._deprecated;
var valueOrDefault = helpers.valueOrDefault;

defaults._set("bar", {
    hover: {
        mode: "label",
    },

    scales: {
        xAxes: [
            {
                type: "category",
                offset: true,
                gridLines: {
                    offsetGridLines: true,
                },
            },
        ],

        yAxes: [
            {
                type: "linear",
            },
        ],
    },
});

defaults._set("global", {
    datasets: {
        bar: {
            categoryPercentage: 0.8,
            barPercentage: 0.9,
        },
    },
});

/**
 * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap.
 * @private
 */
function computeMinSampleSize(scale, pixels) {
    var min = scale._length;
    var prev, curr, i, ilen;

    for (i = 1, ilen = pixels.length; i < ilen; ++i) {
        min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1]));
    }

    for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) {
        curr = scale.getPixelForTick(i);
        min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min;
        prev = curr;
    }

    return min;
}

/**
 * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null,
 * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This
 * mode currently always generates bars equally sized (until we introduce scriptable options?).
 * @private
 */
function computeFitCategoryTraits(index, ruler, options, stackCount) {
    var thickness = options.barThickness;
    var curr = ruler.pixels[index];
    var min = helpers.isNullOrUndef(thickness)
        ? computeMinSampleSize(ruler.scale, ruler.pixels)
        : -1;
    var size, ratio;

    if (helpers.isNullOrUndef(thickness)) {
        size = min * options.categoryPercentage;
        ratio = options.barPercentage;
    } else {
        // When bar thickness is enforced, category and bar percentages are ignored.
        // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%')
        // and deprecate barPercentage since this value is ignored when thickness is absolute.
        size = thickness * stackCount;
        ratio = 1;
    }

    return {
        chunk: size / stackCount,
        ratio: ratio,
        start: curr - size / 2,
    };
}

/**
 * Computes an "optimal" category that globally arranges bars side by side (no gap when
 * percentage options are 1), based on the previous and following categories. This mode
 * generates bars with different widths when data are not evenly spaced.
 * @private
 */
function computeFlexCategoryTraits(index, ruler, options, stackCount) {
    var pixels = ruler.pixels;
    var curr = pixels[index];
    var prev = index > 0 ? pixels[index - 1] : null;
    var next = index < pixels.length - 1 ? pixels[index + 1] : null;
    var percent = options.categoryPercentage;
    var start, size;

    if (prev === null) {
        // first data: its size is double based on the next point or,
        // if it's also the last data, we use the scale size.
        prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
    }

    if (next === null) {
        // last data: its size is also double based on the previous point.
        next = curr + curr - prev;
    }

    start = curr - ((curr - Math.min(prev, next)) / 2) * percent;
    size = (Math.abs(next - prev) / 2) * percent;

    return {
        chunk: size / stackCount,
        ratio: options.barPercentage,
        start: start,
    };
}

module.exports = DatasetController.extend({
    dataElementType: elements.Rectangle,

    /**
     * @private
     */
    _dataElementOptions: [
        "backgroundColor",
        "borderColor",
        "borderSkipped",
        "borderWidth",
        "barPercentage",
        "barThickness",
        "categoryPercentage",
        "maxBarThickness",
        "minBarLength",
    ],

    initialize: function () {
        var me = this;
        var meta, scaleOpts;

        DatasetController.prototype.initialize.apply(me, arguments);

        meta = me.getMeta();
        meta.stack = me.getDataset().stack;
        meta.bar = true;

        scaleOpts = me._getIndexScale().options;
        deprecated(
            "bar chart",
            scaleOpts.barPercentage,
            "scales.[x/y]Axes.barPercentage",
            "dataset.barPercentage"
        );
        deprecated(
            "bar chart",
            scaleOpts.barThickness,
            "scales.[x/y]Axes.barThickness",
            "dataset.barThickness"
        );
        deprecated(
            "bar chart",
            scaleOpts.categoryPercentage,
            "scales.[x/y]Axes.categoryPercentage",
            "dataset.categoryPercentage"
        );
        deprecated(
            "bar chart",
            me._getValueScale().options.minBarLength,
            "scales.[x/y]Axes.minBarLength",
            "dataset.minBarLength"
        );
        deprecated(
            "bar chart",
            scaleOpts.maxBarThickness,
            "scales.[x/y]Axes.maxBarThickness",
            "dataset.maxBarThickness"
        );
    },

    update: function (reset) {
        var me = this;
        var rects = me.getMeta().data;
        var i, ilen;

        me._ruler = me.getRuler();

        for (i = 0, ilen = rects.length; i < ilen; ++i) {
            me.updateElement(rects[i], i, reset);
        }
    },

    updateElement: function (rectangle, index, reset) {
        var me = this;
        var meta = me.getMeta();
        var dataset = me.getDataset();
        var options = me._resolveDataElementOptions(rectangle, index);

        rectangle._xScale = me.getScaleForId(meta.xAxisID);
        rectangle._yScale = me.getScaleForId(meta.yAxisID);
        rectangle._datasetIndex = me.index;
        rectangle._index = index;
        rectangle._model = {
            backgroundColor: options.backgroundColor,
            borderColor: options.borderColor,
            borderSkipped: options.borderSkipped,
            borderWidth: options.borderWidth,
            datasetLabel: dataset.label,
            label: me.chart.data.labels[index],
        };

        if (helpers.isArray(dataset.data[index])) {
            rectangle._model.borderSkipped = null;
        }

        me._updateElementGeometry(rectangle, index, reset, options);

        rectangle.pivot();
    },

    /**
     * @private
     */
    _updateElementGeometry: function (rectangle, index, reset, options) {
        var me = this;
        var model = rectangle._model;
        var vscale = me._getValueScale();
        var base = vscale.getBasePixel();
        var horizontal = vscale.isHorizontal();
        var ruler = me._ruler || me.getRuler();
        var vpixels = me.calculateBarValuePixels(me.index, index, options);
        var ipixels = me.calculateBarIndexPixels(
            me.index,
            index,
            ruler,
            options
        );

        model.horizontal = horizontal;
        model.base = reset ? base : vpixels.base;
        model.x = horizontal ? (reset ? base : vpixels.head) : ipixels.center;
        model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
        model.height = horizontal ? ipixels.size : undefined;
        model.width = horizontal ? undefined : ipixels.size;
    },

    /**
     * Returns the stacks based on groups and bar visibility.
     * @param {number} [last] - The dataset index
     * @returns {string[]} The list of stack IDs
     * @private
     */
    _getStacks: function (last, dataIndex) {
        var me = this;
        var scale = me._getIndexScale();
        var metasets = scale._getMatchingVisibleMetas(me._type);
        var stacked = scale.options.stacked;
        var ilen = metasets.length;
        var stacks = [];
        var i, meta;

        for (i = 0; i < ilen; ++i) {
            meta = metasets[i];
            if (
                typeof dataIndex !== "undefined" &&
                helpers.isNullOrUndef(
                    me.chart.data.datasets[meta.index].data[dataIndex]
                )
            ) {
                continue;
            }
            // stacked   | meta.stack
            //           | found | not found | undefined
            // false     |   x   |     x     |     x
            // true      |       |     x     |
            // undefined |       |     x     |     x
            if (
                stacked === false ||
                stacks.indexOf(meta.stack) === -1 ||
                (stacked === undefined && meta.stack === undefined)
            ) {
                stacks.push(meta.stack);
            }
            if (meta.index === last) {
                break;
            }
        }
        return stacks;
    },

    /**
     * Returns the effective number of stacks based on groups and bar visibility.
     * @private
     */
    getStackCount: function (index) {
        return this._getStacks(undefined, index).length;
    },

    /**
     * Returns the stack index for the given dataset based on groups and bar visibility.
     * @param {number} [datasetIndex] - The dataset index
     * @param {string} [name] - The stack name to find
     * @returns {number} The stack index
     * @private
     */
    getStackIndex: function (datasetIndex, name) {
        var stacks = this._getStacks(datasetIndex);
        var index = name !== undefined ? stacks.indexOf(name) : -1; // indexOf returns -1 if element is not present

        return index === -1 ? stacks.length - 1 : index;
    },

    /**
     * @private
     */
    getRuler: function () {
        var me = this;
        var scale = me._getIndexScale();
        var pixels = [];
        var i, ilen;

        for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
            pixels.push(scale.getPixelForValue(null, i, me.index));
        }

        return {
            pixels: pixels,
            start: scale._startPixel,
            end: scale._endPixel,
            stackCount: me.getStackCount(),
            scale: scale,
        };
    },

    /**
     * Note: pixel values are not clamped to the scale area.
     * @private
     */
    calculateBarValuePixels: function (datasetIndex, index, options) {
        var me = this;
        var chart = me.chart;
        var scale = me._getValueScale();
        var isHorizontal = scale.isHorizontal();
        var datasets = chart.data.datasets;
        var metasets = scale._getMatchingVisibleMetas(me._type);
        var value = scale._parseValue(datasets[datasetIndex].data[index]);
        var minBarLength = options.minBarLength;
        var stacked = scale.options.stacked;
        var stack = me.getMeta().stack;
        var start =
            value.start === undefined
                ? 0
                : value.max >= 0 && value.min >= 0
                ? value.min
                : value.max;
        var length =
            value.start === undefined
                ? value.end
                : value.max >= 0 && value.min >= 0
                ? value.max - value.min
                : value.min - value.max;
        var ilen = metasets.length;
        var i, imeta, ivalue, base, head, size, stackLength;

        if (stacked || (stacked === undefined && stack !== undefined)) {
            for (i = 0; i < ilen; ++i) {
                imeta = metasets[i];

                if (imeta.index === datasetIndex) {
                    break;
                }

                if (imeta.stack === stack) {
                    stackLength = scale._parseValue(
                        datasets[imeta.index].data[index]
                    );
                    ivalue =
                        stackLength.start === undefined
                            ? stackLength.end
                            : stackLength.min >= 0 && stackLength.max >= 0
                            ? stackLength.max
                            : stackLength.min;

                    if (
                        (value.min < 0 && ivalue < 0) ||
                        (value.max >= 0 && ivalue > 0)
                    ) {
                        start += ivalue;
                    }
                }
            }
        }

        base = scale.getPixelForValue(start);
        head = scale.getPixelForValue(start + length);
        size = head - base;

        if (minBarLength !== undefined && Math.abs(size) < minBarLength) {
            size = minBarLength;
            if (
                (length >= 0 && !isHorizontal) ||
                (length < 0 && isHorizontal)
            ) {
                head = base - minBarLength;
            } else {
                head = base + minBarLength;
            }
        }

        return {
            size: size,
            base: base,
            head: head,
            center: head + size / 2,
        };
    },

    /**
     * @private
     */
    calculateBarIndexPixels: function (datasetIndex, index, ruler, options) {
        var me = this;
        const stackCount = true ? me.getStackCount(index) : ruler.stackCount;

        var range =
            options.barThickness === "flex"
                ? computeFlexCategoryTraits(index, ruler, options, stackCount)
                : computeFitCategoryTraits(index, ruler, options, stackCount);

        var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack);

        var center = range.start + range.chunk * stackIndex + range.chunk / 2;
        console.log(datasetIndex, index, stackIndex, center, range);
        var size = Math.min(
            valueOrDefault(options.maxBarThickness, Infinity),
            range.chunk * range.ratio
        );

        return {
            base: center - size / 2,
            head: center + size / 2,
            center: center,
            size: size,
        };
    },

    draw: function () {
        var me = this;
        var chart = me.chart;
        var scale = me._getValueScale();
        var rects = me.getMeta().data;
        var dataset = me.getDataset();
        var ilen = rects.length;
        var i = 0;

        helpers.canvas.clipArea(chart.ctx, chart.chartArea);

        for (; i < ilen; ++i) {
            var val = scale._parseValue(dataset.data[i]);
            if (!isNaN(val.min) && !isNaN(val.max)) {
                rects[i].draw();
            }
        }

        helpers.canvas.unclipArea(chart.ctx);
    },

    /**
     * @private
     */
    _resolveDataElementOptions: function () {
        var me = this;
        var values = helpers.extend(
            {},
            DatasetController.prototype._resolveDataElementOptions.apply(
                me,
                arguments
            )
        );
        var indexOpts = me._getIndexScale().options;
        var valueOpts = me._getValueScale().options;

        values.barPercentage = valueOrDefault(
            indexOpts.barPercentage,
            values.barPercentage
        );
        values.barThickness = valueOrDefault(
            indexOpts.barThickness,
            values.barThickness
        );
        values.categoryPercentage = valueOrDefault(
            indexOpts.categoryPercentage,
            values.categoryPercentage
        );
        values.maxBarThickness = valueOrDefault(
            indexOpts.maxBarThickness,
            values.maxBarThickness
        );
        values.minBarLength = valueOrDefault(
            valueOpts.minBarLength,
            values.minBarLength
        );

        return values;
    },
});
4

0 に答える 0