Search code examples
javascriptcanvashtml5-canvasdraw

How to change order of text to appear on arc - context 2d, and stop fill style overwriting on the canvas 2d (using with chart.js)?


So I trying to get build an arc with text on it as rounded rectangle in context 2d but the text is going under it and is getting hidden, also, the fill style for arc is getting overridden by the fill style for text. Here is what I am getting and what I want:

enter image description here

Here is the js fiddle with the code : https://jsfiddle.net/abhishek_soni/vzs6dLy9/19/

Here is the same code :

Html code :

<canvas></canvas>

Js code :

var roundRect = function(ctx, x, y, w, h, r) {
  var x2 = x + w;
  var y2 = y + h;

  if (w < 2 * r) r = w / 2;
  if (h < 2 * r) r = h / 2;

  ctx.beginPath();
  ctx.moveTo(x + r, y);
  ctx.arcTo(x2, y, x2, y2, r);
  ctx.arcTo(x2, y2, x, y2, r);
  ctx.arcTo(x, y2, x, y, r);
  ctx.arcTo(x, y, x2, y, r);
};

var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');

context.save();
roundRect(context, 0, 0, 50, 60, 20);
context.fillStyle = 'red'; // This should work, but it doesn't 
context.fill();
context.restore();

context.fillStyle = 'green';
context.fillText('Hey', 50, 80) // if I change this to 50 it hides behind the arc, which I don't want.
context.textAlign = "center";
context.fill();

context.save();
roundRect(context, 100, 100, 50, 90, 20);
context.fillStyle = 'red'; // This should work, but it doesn't 
context.fill();
context.restore();

context.fillStyle = 'green';
context.fillText('Hey', 90, 100) // if I change this to 50 it hides behind the arc, which I don't want.
context.textAlign = "center";
context.fill();

context.save();
roundRect(context, 300, 100, 50, 120, 20);
context.fillStyle = 'red'; // This should work, but it doesn't 
context.fill();
context.restore();

context.fillStyle = 'green';
context.fillText('Hey', 90, 130) // if I change this to 50 it hides behind the arc, which I don't want.
context.textAlign = "center";
context.fill();


Solution

  • The problem is you are calling fill() after you create the text which you don't need. That fill() is being applied to the shape drawn previously. The purpose of methods like fillText() and fillRect() are to allow you to draw without the need to call fill() after.

    Don't call fill after the text and it works.

    Next to align your text in the center of the shape I would calculate the center and place the text there and then use context.textAlign = 'center'; to center it.

    Here's an example

    var canvas = document.querySelector('canvas');
    var context = canvas.getContext('2d');
    canvas.width = innerWidth;
    canvas.height = innerHeight;
    
    let center = {}
    var roundRect = function(ctx, x, y, w, h, r) {
      var x2 = x + w;
      var y2 = y + h;
      center = {x: x + w/2, y: y + h/2}
      
      if (w < 2 * r) r = w / 2;
      if (h < 2 * r) r = h / 2;
    
      ctx.beginPath();
      ctx.moveTo(x + r, y);
      ctx.arcTo(x2, y, x2, y2, r);
      ctx.arcTo(x2, y2, x, y2, r);
      ctx.arcTo(x, y2, x, y, r);
      ctx.arcTo(x, y, x2, y, r);
      ctx.closePath();
    };
    
    
    
    context.fillStyle = 'red';
    roundRect(context, 0, 0, 50, 60, 20);
    context.fill();
    
    context.fillStyle = 'green';
    context.textAlign = 'center';
    context.fillText('Hey', center.x, center.y);
    context.textAlign = "center";
    
    context.fillStyle = 'red';
    roundRect(context, 100, 100, 50, 90, 20);
    context.fill();
    
    context.fillStyle = 'green';
    context.textAlign = 'center';
    context.fillText('Hey', center.x, center.y)
    context.textAlign = "center";
    
    context.fillStyle = 'red';
    roundRect(context, 300, 100, 50, 120, 20);
    context.fill();
    
    context.fillStyle = 'green';
    context.textAlign = 'center';
    context.fillText('Hey', center.x, center.y);
    context.textAlign = "center";
    <canvas id="canvas"></canvas>

    I'm also going to add an example using a class to do this. I prefer this over the other method. If you don't need different color shapes you can get rid of the color argument. And if you want different color text you can add one for that.

    var canvas = document.querySelector("canvas");
    var ctx = canvas.getContext("2d");
    canvas.width = innerWidth;
    canvas.height = innerHeight;
    
    class roundRect {
      constructor(x, y, w, h, r, c) {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.x2 = this.x + this.w;
        this.y2 = this.y + this.h;
        this.center = { x: x + w / 2, y: y + h / 2 };
        this.r = r;
        this.color = c;
      }
      drawShape() {
        if (this.w < 2 * this.r) this.r = this.w / 2;
        if (this.h < 2 * this.r) this.r = this.h / 2;
        
        ctx.fillStyle = this.color;
        ctx.beginPath();
        ctx.moveTo(this.x + this.r, this.y);
        ctx.arcTo(this.x2, this.y, this.x2, this.y2, this.r);
        ctx.arcTo(this.x2, this.y2, this.x, this.y2, this.r);
        ctx.arcTo(this.x, this.y2, this.x, this.y, this.r);
        ctx.arcTo(this.x, this.y, this.x2, this.y, this.r);
        ctx.closePath();
        ctx.fill();
      }
      drawText() {
        ctx.fillStyle = 'green';
        ctx.textAlign = "center";
        ctx.textBaseline = 'middle';
        ctx.fillText("Hey", this.center.x, this.center.y); 
      }
    }
    
    let rect1 = new roundRect(0, 0, 50, 60, 20, 'red', 'Text')
    let rect2 = new roundRect(60, 0, 50, 80, 20, 'lightblue', 'Text')
    let rect3 = new roundRect(120, 0, 50, 100, 20, 'lightgreen', 'Text')
    
    rect1.drawShape();
    rect1.drawText();
    rect2.drawShape();
    rect2.drawText();
    rect3.drawShape();
    rect3.drawText();
    <canvas id="canvas"></canvas>

    EDIT:

    To rotate your text use the following.

    var canvas = document.querySelector('canvas');
    var context = canvas.getContext('2d');
    canvas.width = innerWidth;
    canvas.height = innerHeight;
    
    let center = {}
    var roundRect = function(ctx, x, y, w, h, r) {
      var x2 = x + w;
      var y2 = y + h;
      center = {x: x + w/2, y: y + h/2}
      
      if (w < 2 * r) r = w / 2;
      if (h < 2 * r) r = h / 2;
    
      ctx.beginPath();
      ctx.moveTo(x + r, y);
      ctx.arcTo(x2, y, x2, y2, r);
      ctx.arcTo(x2, y2, x, y2, r);
      ctx.arcTo(x, y2, x, y, r);
      ctx.arcTo(x, y, x2, y, r);
      ctx.closePath();
    };
    
    
    
    context.fillStyle = 'red';
    roundRect(context, 0, 0, 50, 60, 20);
    context.fill();
    
    context.fillStyle = 'green';
    context.textAlign = 'center';
    context.fillText('Hey', center.x, center.y);
    context.textAlign = "center";
    
    context.fillStyle = 'red';
    roundRect(context, 100, 100, 50, 90, 20);
    context.fill();
    
    context.fillStyle = 'green';
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.save() //save before you transform something
    context.translate(center.x, center.y) //use this to position
    context.rotate(degToRad(90)) //rotate here using degrees
    context.fillText('Hey', 0, 0) //draw this a (0, 0)
    context.fillStyle = 'lightblue'; //delete this
    context.fillRect(0, 0, canvas.width, canvas.height) //and this
    context.restore() //restore when done with all objects you want affected
    context.textAlign = "center";
    
    
    context.fillStyle = 'red';
    roundRect(context, 300, 100, 50, 120, 20);
    context.fill();
    
    context.fillStyle = 'green';
    context.textAlign = 'center';
    context.fillText('Hey', center.x, center.y);
    context.textAlign = "center";
    
    function degToRad(deg) {
      return deg * (Math.PI/180)
    }
    <canvas id="canvas"></canvas>