1 thought on “chart is out of the canvas

  1. Hello all.

    I’ve been investigated how to make a hotfix to solve that. I saw three strange behaviors:

    1- The data breaks if have heavy changes, for example:

    [0, 10, 70, 30, 20, 12, 50, 200, 200, 200, 200, 200]

    2- The ‘error’ is always the half of the height of our borderWidth.

    3- That error may was introduced when we changed the line 36 of core.layoutService.js on the next COMMIT

    In second place I saw two strange padding vars inside the function ‘update’ of the core.layoutService:

    Around the line 29 we have the function and on lines 35 and 36 we have the strange vars.

    // The most important function
            update: function(chartInstance, width, height) {
                if (!chartInstance) {
                    return;
                }
    
                var xPadding = 0;
                var yPadding = 0;

    I have been seen what the function does with that vars, specially with yPadding. I couldn’t se any useful calc done with yPadding. In my opinion here is the problem. We always maintain the yPadding to 0 and IMHO it was an error applied in another version of that file HERE .

    The solution is that follows and I developed that because is the demand behind the developer of that commit deleted the line.

    THAT’S A HOTFIX, I WILL MAKE A PR AND IF IT IS APPROVED IT WILL BE THE SOLUTION

    If you apply that code you will can set your padding on the styles config of the chart, also, if you don’t apply any config he will take a default based on the chart height.

    Config prop example:

    padding: {
      x: 5,
      y: 4
    },

    Once we have loaded the ChartJS lib, overwrite the method doing the next:

            Chart.layoutService.update = function(chartInstance, width, height) {
    //PATCH to have helpers here
        var helpers = Chart.helpers;
                if (!chartInstance) {
                    return;
                }
                 var paddingX = chartInstance.config.options.padding ? chartInstance.config.options.padding.x : 0,
               paddingY = chartInstance.config.options.padding ? chartInstance.config.options.padding.y : 0,
               validYConfigPadding = paddingY < height / 2 && (paddingY > 0),
               validXConfigPadding = paddingX < width / 2 && (paddingX > 0),
               xPadding = (validXConfigPadding ? paddingX : (height > 30 ? 5 : 2)),
                     yPadding = (validYConfigPadding ? paddingY : (height > 30 ? 5 : 2));
    
                var leftBoxes = helpers.where(chartInstance.boxes, function(box) {
                    return box.options.position === "left";
                });
                var rightBoxes = helpers.where(chartInstance.boxes, function(box) {
                    return box.options.position === "right";
                });
                var topBoxes = helpers.where(chartInstance.boxes, function(box) {
                    return box.options.position === "top";
                });
                var bottomBoxes = helpers.where(chartInstance.boxes, function(box) {
                    return box.options.position === "bottom";
                });
    
                // Boxes that overlay the chart area such as the radialLinear scale
                var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) {
                    return box.options.position === "chartArea";
                });
    
                // Ensure that full width boxes are at the very top / bottom
                topBoxes.sort(function(a, b) {
                    return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0);
                });
                bottomBoxes.sort(function(a, b) {
                    return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0);
                });
    
                // Essentially we now have any number of boxes on each of the 4 sides.
                // Our canvas looks like the following.
                // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
                // B1 is the bottom axis
                // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
                // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
                // an error will be thrown.
                //
                // |----------------------------------------------------|
                // |                  T1 (Full Width)                   |
                // |----------------------------------------------------|
                // |    |    |                 T2                  |    |
                // |    |----|-------------------------------------|----|
                // |    |    | C1 |                           | C2 |    |
                // |    |    |----|                           |----|    |
                // |    |    |                                     |    |
                // | L1 | L2 |           ChartArea (C0)            | R1 |
                // |    |    |                                     |    |
                // |    |    |----|                           |----|    |
                // |    |    | C3 |                           | C4 |    |
                // |    |----|-------------------------------------|----|
                // |    |    |                 B1                  |    |
                // |----------------------------------------------------|
                // |                  B2 (Full Width)                   |
                // |----------------------------------------------------|
                //
                // What we do to find the best sizing, we do the following
                // 1. Determine the minimum size of the chart area.
                // 2. Split the remaining width equally between each vertical axis
                // 3. Split the remaining height equally between each horizontal axis
                // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
                // 5. Adjust the sizes of each axis based on it's minimum reported size.
                // 6. Refit each axis
                // 7. Position each axis in the final location
                // 8. Tell the chart the final location of the chart area
                // 9. Tell any axes that overlay the chart area the positions of the chart area
    
                // Step 1
                var chartWidth = width - (2 * xPadding);
                var chartHeight = height - (2 * yPadding);
                var chartAreaWidth = chartWidth / 2; // min 50%%
                var chartAreaHeight = chartHeight / 2; // min 50%%
    
                // Step 2
                var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
    
                // Step 3
                var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
    
                // Step 4
                var maxChartAreaWidth = chartWidth;
                var maxChartAreaHeight = chartHeight;
                var minBoxSizes = [];
    
                helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
    
                function getMinimumBoxSize(box) {
                    var minSize;
                    var isHorizontal = box.isHorizontal();
    
                    if (isHorizontal) {
                        minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
                        maxChartAreaHeight -= minSize.height;
                    } else {
                        minSize = box.update(verticalBoxWidth, chartAreaHeight);
                        maxChartAreaWidth -= minSize.width;
                    }
    
                    minBoxSizes.push({
                        horizontal: isHorizontal,
                        minSize: minSize,
                        box: box
                    });
                }
    
                // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
                // be if the axes are drawn at their minimum sizes.
    
                // Steps 5 & 6
                var totalLeftBoxesWidth = xPadding;
                var totalRightBoxesWidth = xPadding;
                var totalTopBoxesHeight = yPadding;
                var totalBottomBoxesHeight = yPadding;
    
                // Update, and calculate the left and right margins for the horizontal boxes
                helpers.each(leftBoxes.concat(rightBoxes), fitBox);
    
                helpers.each(leftBoxes, function(box) {
                    totalLeftBoxesWidth += box.width;
                });
    
                helpers.each(rightBoxes, function(box) {
                    totalRightBoxesWidth += box.width;
                });
    
                // Set the Left and Right margins for the horizontal boxes
                helpers.each(topBoxes.concat(bottomBoxes), fitBox);
    
                // Function to fit a box
                function fitBox(box) {
                    var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) {
                        return minBoxSize.box === box;
                    });
    
                    if (minBoxSize) {
                        if (box.isHorizontal()) {
                            var scaleMargin = {
                                left: totalLeftBoxesWidth,
                                right: totalRightBoxesWidth,
                                top: 0,
                                bottom: 0
                            };
    
                            // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
                            // on the margin. Sometimes they need to increase in size slightly
                            box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
                        } else {
                            box.update(minBoxSize.minSize.width, maxChartAreaHeight);
                        }
                    }
                }
    
                // Figure out how much margin is on the top and bottom of the vertical boxes
                helpers.each(topBoxes, function(box) {
                    totalTopBoxesHeight += box.height;
                });
    
                helpers.each(bottomBoxes, function(box) {
                    totalBottomBoxesHeight += box.height;
                });
    
                // Let the left layout know the final margin
                helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
    
                function finalFitVerticalBox(box) {
                    var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) {
                        return minBoxSize.box === box;
                    });
    
                    var scaleMargin = {
                        left: 0,
                        right: 0,
                        top: totalTopBoxesHeight,
                        bottom: totalBottomBoxesHeight
                    };
    
                    if (minBoxSize) {
                        box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
                    }
                }
    
                // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
                totalLeftBoxesWidth = xPadding;
                totalRightBoxesWidth = xPadding;
                totalTopBoxesHeight = yPadding;
                totalBottomBoxesHeight = yPadding;
    
                helpers.each(leftBoxes, function(box) {
                    totalLeftBoxesWidth += box.width;
                });
    
                helpers.each(rightBoxes, function(box) {
                    totalRightBoxesWidth += box.width;
                });
    
                helpers.each(topBoxes, function(box) {
                    totalTopBoxesHeight += box.height;
                });
                helpers.each(bottomBoxes, function(box) {
                    totalBottomBoxesHeight += box.height;
                });
    
                // Figure out if our chart area changed. This would occur if the dataset layout label rotation
                // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
                // without calling `fit` again
                var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
                var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
    
                if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
                    helpers.each(leftBoxes, function(box) {
                        box.height = newMaxChartAreaHeight;
                    });
    
                    helpers.each(rightBoxes, function(box) {
                        box.height = newMaxChartAreaHeight;
                    });
    
                    helpers.each(topBoxes, function(box) {
                        if (!box.options.fullWidth) {
                            box.width = newMaxChartAreaWidth;
                        }
                    });
    
                    helpers.each(bottomBoxes, function(box) {
                        if (!box.options.fullWidth) {
                            box.width = newMaxChartAreaWidth;
                        }
                    });
    
                    maxChartAreaHeight = newMaxChartAreaHeight;
                    maxChartAreaWidth = newMaxChartAreaWidth;
                }
    
                // Step 7 - Position the boxes
                var left = xPadding;
                var top = yPadding;
                var right = 0;
                var bottom = 0;
    
                helpers.each(leftBoxes.concat(topBoxes), placeBox);
    
                // Account for chart width and height
                left += maxChartAreaWidth;
                top += maxChartAreaHeight;
    
                helpers.each(rightBoxes, placeBox);
                helpers.each(bottomBoxes, placeBox);
    
                function placeBox(box) {
                    if (box.isHorizontal()) {
                        box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth;
                        box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth;
                        box.top = top;
                        box.bottom = top + box.height;
    
                        // Move to next point
                        top = box.bottom;
    
                    } else {
    
                        box.left = left;
                        box.right = left + box.width;
                        box.top = totalTopBoxesHeight;
                        box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
    
                        // Move to next point
                        left = box.right;
                    }
                }
    
                // Step 8
                chartInstance.chartArea = {
                    left: totalLeftBoxesWidth,
                    top: totalTopBoxesHeight,
                    right: totalLeftBoxesWidth + maxChartAreaWidth,
                    bottom: totalTopBoxesHeight + maxChartAreaHeight
                };
    
                // Step 9
                helpers.each(chartAreaBoxes, function(box) {
                    box.left = chartInstance.chartArea.left;
                    box.top = chartInstance.chartArea.top;
                    box.right = chartInstance.chartArea.right;
                    box.bottom = chartInstance.chartArea.bottom;
    
                    box.update(maxChartAreaWidth, maxChartAreaHeight);
                });
            }

Comments are closed.