Josh
Josh

Reputation: 618

KonvaJS always center text in label

I apologize for the code, it's in a state I'm actively playing with.

In Konva JS, I'm creating labels. Which is basically a group, it has a shape and text element. The shapes are drawn dynamically from a database, and each db row has the coordinates, the shape (square, circle etc), size and rotation. These are multiplied by a scale depending on screen size. I had a working solution, which I've been using for weeks, but I also have a function that changes the shape of a shape. So I press a button, it updates the database (e.g changes square to circle) and then redraws the canvas. Because going from a square to a circle changes from a shape that's drawn from the top left to one that's drawn from center, it messes up the coordinates. I decided to switch to drawing rectangles with an offset of width/2 so that rectangles are drawn from the center too.

I have subsequently kept breaking it further and I now can't get text centered at all. I need text centered regardless of shape, size or rotation.

current code

        //Gets table array from SQL
        tables = callPhpFunction('getTablesInRoom', room);
        tables = JSON.parse(tables);
        
        numberOfTables = tables.length;
        index = 0;
        
        tableLayer = new Konva.Layer();
        tableLayer.add(transformer);
        
        //For every table
        while(index < numberOfTables){
            tableNumber = tables[index]['tablenumber'];
            offset_x = 0;
            offset_y = 0;
            pos_x = parseInt(tables[index]['posx']) * scale;
            pos_y = parseInt(tables[index]['posy']) * scale;
            shape = tables[index]['shape'];     
            tableWidth = parseInt(tables[index]['width']) * scale;
            tableHeight = parseInt(tables[index]['height']) * scale;
            rotation = parseInt(tables[index]['rotation']); 
            fillColor = "gray";
            strokeColor = "black";
                        
    
            var table = new Konva.Label({
                x: pos_x,
                y: pos_y,
                draggable:canDrag
              });

            pos_x = 0;
            pos_y = 0 ;
            
            //If the shape is a square, rectangle, or undefined
            if(shape != "circle" && shape != "ellipse" && shape != "longellipse"){
                offset_x = tableWidth/2
                offset_y = tableHeight/2
            
                table.add(new Konva.Rect({
                    width: tableWidth,
                    height: tableHeight,
                    offsetX: offset_y,
                    offsetY: offset_y,
                    height: tableHeight,
                    fill: fillColor,
                    stroke: strokeColor,
                    strokeWidth: 4
                }));
            //If shape is a circle or ellipse
            } else {
                
                table.add(new Konva.Ellipse({
                    radiusX: tableWidth/2,
                    radiusY: tableHeight/2,
                    fill: fillColor,
                    stroke: strokeColor,
                    strokeWidth: 4
                }));
                
                //Can't remember why this is here, but it seems to work/ Think if these are reused when the text is drawn it centers them. 
                pos_x = -tableWidth/2;
                pos_y = -tableHeight/2;
            }
            //Gets the first shape (ellipse or rectangle) in the label. I think is equivalent to getTag()
            innerShape = getInnerShape(table);
            
            //Rotates the inner shape, while leaving text unRotated. 
            if(rotation != 0){
                canvasX =  pos_x + (tableWidth/2)
                canvasY =  pos_y + (tableHeight/2)
                rotateAroundPoint(innerShape, rotation, {x:canvasX, y:canvasY });
            }
            
            //Currently approach is to get center based on the client rectangle. It's not working
            box = table.getClientRect();
            box_x = box.x + box.width / 2;
            box_y = box.y + box.height / 2; 
                            
            table.add(new Konva.Text({
                width: tableWidth,
                height: tableHeight,
                x: box_x,
                y: box_y,
                text: tableNumber,
                verticalAlign: 'middle',
                align: 'center',
                fontSize: 24,
                fontFamily: 'Calibri',
                fill: 'black',
                listening: false
            }))

And this is the visual result Not remotely centers

Upvotes: 1

Views: 2304

Answers (1)

Ingmars
Ingmars

Reputation: 998

I slightly rewrote your creation routine; seems working well like this. The essence here is that shape + label are grouped together and global [scaled] position is applied to those groups, not individual graphic elements.

Code:

    const tableLayer = new Konva.Layer();
    
    const scale = 1.0; // position scale factor
    
    const mockData = [
        {
            label: "45",
            shape: "circle",
            x: 100,
            y: 150,
            w: 100,
            h: 100,
            r: 0
        },
        {
            label: "46",
            shape: "rect",
            x: 200,
            y: 170,
            w: 150,
            h: 100,
            r: -30
        },
        {
            label: "47",
            shape: "rect",
            x: 70,
            y: 200,
            w: 50,
            h: 100,
            r: 15
        },
        {
            label: "48",
            shape: "ellipse",
            x: 400,
            y: 300,
            w: 250,
            h: 150,
            r: 30
        }
    ];


    function createShapeElement( data ) {
        // Create container group
        let element = new Konva.Group( {
            x: data.x * scale,
            y: data.y * scale,
            draggable: true,
            listening: true,
        } );

        // Create background shape
        let shape;
        switch ( data.shape ) {
            case "circle": // fall-through
            case "ellipse": // fall-through
            case "longellipse":
                shape = new Konva.Ellipse( {
                    x: 0,
                    y: 0,
                    radiusX: data.w * 0.5,
                    radiusY: data.h * 0.5,
                    rotation: data.r,
                    fill: "gray",
                    stroke: "black",
                    strokeWidth: 4,
                    draggable: false,
                    listening: true
                } );
                break;

            default:
                shape = new Konva.Rect( {
                    x: 0,
                    y: 0,
                    offsetX: data.w * 0.5,
                    offsetY: data.h * 0.5,
                    width: data.w,
                    height: data.h,
                    rotation: data.r,
                    fill: "gray",
                    stroke: "black",
                    strokeWidth: 4,
                    draggable: false,
                    listening: true
                } );
                break;
        } // End switch

        // Create label
        let label = new Konva.Text( {
            x: data.w * -0.5,
            y: data.h * -0.5,
            width: data.w,
            height: data.h,
            text: data.label,
            fontSize: 24,
            fill: "black",
            align: "center",
            verticalAlign: "middle",
            draggable: false,
            listening: false
        } );

        element.add( shape, label );

        return element;
    }


    // Loop your data and call the creation method for each data item.
    mockData.forEach( itemData => {
        tableLayer.add( createShapeElement( itemData ) );
    } );

Upvotes: 5

Related Questions