Ad

How To Make Canvas Drawing Always Center

I'm working on the visualization of a box inside another box.

I did the props for changing the height, width, length of the outer box and the buffer props for changing the size inner box.

How should I draw both boxes centered on the canvas (outer box and inner box when changing buffers)?

I did the height buffer and it's working I guess because when I increase this value, inner box is always in the middle of the outer box, but can't figure out how to make it same when I change width or length buffers :/

Here's my component code:

import React, { Component } from 'react';
import './CanvasBox.css';

class CanvasBox extends Component {
    constructor(props) {
        super(props);
        this.canvas = React.createRef();
        this.state = {
            x1: this.props.x1,
            x2: this.props.x2,
            y: this.props.y,
            bufferLength: this.props.bufferLength || 5,
            bufferWidth: this.props.bufferWidth || 5,
            bufferHeight: this.props.bufferHeight || 5,
            color: this.props.color || '#ff8d4b'
        };
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        if (
            this.props.x1 !== prevProps.x1 ||
            this.props.x2 !== prevProps.x2 ||
            this.props.y !== prevProps.y ||
            this.props.bufferLength !== prevProps.bufferLength ||
            this.props.bufferWidth !== prevProps.bufferWidth ||
            this.props.bufferHeight !== prevProps.bufferHeight
        ) {
            this.setState(
                {
                    x1: this.props.x1,
                    x2: this.props.x2,
                    y: this.props.y,
                    bufferLength: this.props.bufferLength || 5,
                    bufferWidth: this.props.bufferWidth || 5,
                    bufferHeight: this.props.bufferHeight || 5,
                    color: this.props.color || '#ff8d4b'
                },
                this.draw()
            )
        }
    }

    componentDidMount = () => {
        console.log('componentDidMount')
        const ctx = this.canvas.current.getContext('2d');
        console.log('ctx', ctx)
        this.setState({ ctx }, () => this.draw());
    };

    drawCube = (x, y, wx, wy, h, color, offset) => {
        const ctx = this.state.ctx;
        // left side
        ctx.beginPath();
        ctx.moveTo(x, y);
        ctx.lineTo(x - wx, y - wx * 0.5);
        ctx.lineTo(x - wx, y - h - wx * 0.5);
        ctx.lineTo(x, y - h * 1);
        ctx.closePath();
        ctx.fillStyle = this.shadeColor(color, -10);
        ctx.strokeStyle = color;
        ctx.stroke();
        ctx.fill();

        // right side
        ctx.beginPath();
        ctx.moveTo(x, y);
        ctx.lineTo(x + wy, y - wy * 0.5);
        ctx.lineTo(x + wy, y - h - wy * 0.5);
        ctx.lineTo(x, y - h * 1);
        ctx.closePath();
        ctx.fillStyle = this.shadeColor(color, 10);
        ctx.strokeStyle = this.shadeColor(color, 50);
        ctx.stroke();
        ctx.fill();

        // top
        ctx.beginPath();
        ctx.moveTo(x, y - h);
        ctx.lineTo(x - wx, y - h - wx * 0.5);
        ctx.lineTo(x - wx + wy, y - h - (wx * 0.5 + wy * 0.5));
        ctx.lineTo(x + wy, y - h - wy * 0.5);
        ctx.closePath();
        ctx.globalAlpha = 0.5;
        ctx.fillStyle = this.shadeColor(color, 20);
        ctx.strokeStyle = this.shadeColor(color, 60);
        ctx.stroke();
        ctx.fill();
    };

    shadeColor = (color, percent) => {
        color = color.substr(1);

        const num = parseInt(color, 16),
            amt = Math.round(2.55 * percent),
            R = (num >> 16) + amt,
            G = ((num >> 8) & 0x00ff) + amt,
            B = (num & 0x0000ff) + amt;
        return (
            '#' +
            (
                0x1000000 +
                (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
                (G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
                (B < 255 ? (B < 1 ? 0 : B) : 255)
            )
                .toString(16)
                .slice(1)
        );
    };

    draw = () => {
        // clear the canvas
        this.state.ctx.clearRect(0, 0, this.props.width, this.props.height);

        // draw the cube
        this.drawCube(
            this.props.width/2,
            this.props.height/2 + this.state.y,
            Number(this.state.x1),
            Number(this.state.x2),
            Number(this.state.y),
            '#0000',
            0
        );

        this.drawCube(
            this.props.width/2,
            this.props.height/2 + this.state.y - this.state.bufferHeight,
            Number(this.state.x1) - this.state.bufferLength*2,
            Number(this.state.x2) - this.state.bufferWidth*2,
            Number(this.state.y) - this.state.bufferHeight*2,
            this.state.color,
            0
        );

        requestAnimationFrame(this.draw);

    };

    render() {
        return (
            <div>
                <canvas width={this.props.width} height={this.props.height} ref={this.canvas} />
            </div>
        );
    }
}

export default CanvasBox;

Here's my working example:

https://codepen.io/robson-webdev/pen/xxxRKKM

Many thanks for all advices and help.

Ad

Answer

I am not 100% sure what it is you want however this may help.

First move the canvas origin to the center of the canvas. So if you draw something eg ctx.fillRect(-10,-10,20,20) it is at the center of the canvas. You can do that with ctx.setTransform(1, 0, 0, 1, ctx.canvas.width / 2, ctx.canvas.height / 2)

Then just move your boxes relative to the new origin.

To reset the origin to the top left ctx.setTransform(1,0,0,1,0,0);

The mods needed to do what I think you want can all be done in the draw function.

draw = () => {
    const state = this.state;
    const ctx = state.ctx;

    ctx.setTransform(1,0,0,1,0,0);  // restore top left origin
    ctx.clearRect(0, 0, this.props.width, this.props.height);
    ctx.setTransform(1,0,0,1,ctx.canvas.width / 2, ctx.canvas.height / 2);

    this.drawCube(
        state.x1 / 2 - state.x2 / 2,  // center x
        state.x1 / 4 + state.x2 / 4 + state.y / 2,  // center y
        +(state.x1) ,
        +(state.x2) ,
        +(state.y) ,
        '#0000',
        0
    );
    const x1 = state.x1 - state.bufferLength;
    const y1 = state.x2 - state.bufferWidth;
    const z1 = state.y - state.bufferHeight * 2;
    this.drawCube(
        x1 / 2 - y1 / 2, x1 / 4 + y1 / 4 + z1/ 2,
        x1, y1, z1,
        state.color,
        10
    );

    requestAnimationFrame(this.draw);

};
Ad
source: stackoverflow.com
Ad