Home > Enterprise >  How to display multiple y axis titles on seperate lines and rotated 90 degrees clockwise in chart.js
How to display multiple y axis titles on seperate lines and rotated 90 degrees clockwise in chart.js

Time:03-11

EDIT

My goal is to display several y axis titles on separate lines rotated 90 degrees clockwise in chartjs.

I want the green circled title to be rotated to the orientation of the red circled title while keeping the multi-line title enter image description here.

The default orientation for multiple titles is the orientation shown in the green circle.

I want the title to be on separate lines (like in the green circle) and rotated 90 degrees clockwise (like the orientation of the red circle).

To get the title on separate lines I essentially create an array of strings like so: Add multiple lines as Y axis title in chartJs

y: {
   stacked: true,
   title: {
      text: ['Project1', 'Number of defects', 'Project2'],
      display: true
   }
}

The above code gives the correct title on separate lines but not in the correct orientation.

One solution to rotate the title 90 degrees clockwise is to add a custom title constant and add it as a plugin in the config as follows: Ability to rotate y axis title in chart.js

const customTitle = {
    id: 'customTitle',
    beforeLayout: (chart, args, opts) => {
        const {display,font} = opts;
        if (!display) {
            return;
        }
        const {ctx} = chart;
        ctx.font = font || '12px "Helvetica Neue", Helvetica, Arial, sans-serif'
        const {width} = ctx.measureText(opts.text);
        chart.options.layout.padding.left = width * 1.1;
    },
    afterDraw: (chart, args, opts) => {
        const {font,text,color} = opts;
        const {ctx,chartArea: {top,bottom,left,right}} = chart;
        if (opts.display) {
            ctx.fillStyle = color || Chart.defaults.color
            ctx.font = font || '12px "Helvetica Neue", Helvetica, Arial, sans-serif'
            ctx.fillText(text, 3, (top   bottom) / 2)
        }
    }
}

const labels = ['2021-06-07 00:00:00', '2021-06-08 00:00:00', '2021-06-09 00:00:00'];

const data = {
    labels: labels,
    datasets: [{
        label: 'Fixed defects',
        backgroundColor: 'rgb(0, 255, 0)',
        borderColor: 'rgb(0, 255, 0)',
        data: ['2', '73', '34'],
        barThickness: 5
    }, {
        label: 'Open defects',
        backgroundColor: 'rgb(255, 0, 0)',
        borderColor: 'rgb(255, 0, 0)',
        data: ['0', '5', '2'],
        barThickness: 5

    }]
};

const config = {
    type: 'bar',
    data: data,
    options: {
        scales: {
            x: {
                min: '2021-06-07 00:00:00',
                max: '2021-09-10 00:00:00',
                type: 'time',
                time: {
                    unit: 'week'
                },
                stacked: true,
            },
            y: {
                stacked: true,
            }
        },
        plugins: {
            customTitle: {
                display: true,
                text: ['Project1', 'Number of defects', 'Project2']
            }
        }
    },
    plugins: [customTitle]
};

const myChart = new Chart(
    document.getElementById('myChart'),
    config
);
<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/chart.js@^3"></script>
<script src="https://cdn.jsdelivr.net/npm/moment@^2"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@^1"></script>

<body>
  <div>
    <canvas height="100px" id="myChart"></canvas>
  </div>
</body>

The above code Snippet gives the correct orientation but does not display the titles on separate lines.

However when I try to combine both solutions together I get:

  • The correct rotated 90 degrees title (but not on separate lines) as shown in the red circle on the left

and

  • Multiple titles on separate lines (but not rotated 90 degrees clockwise) as shown in the green circle on the right.

const customTitle = {
    id: 'customTitle',
    beforeLayout: (chart, args, opts) => {
        const {display,font} = opts;
        if (!display) {
            return;
        }
        const {ctx} = chart;
        ctx.font = font || '12px "Helvetica Neue", Helvetica, Arial, sans-serif'
        const {width} = ctx.measureText(opts.text);
        chart.options.layout.padding.left = width * 1.1;
    },
    afterDraw: (chart, args, opts) => {
        const {font,text,color} = opts;
        const {ctx,chartArea: {top,bottom,left,right}} = chart;
        if (opts.display) {
            ctx.fillStyle = color || Chart.defaults.color
            ctx.font = font || '12px "Helvetica Neue", Helvetica, Arial, sans-serif'
            ctx.fillText(text, 3, (top   bottom) / 2)
        }
    }
}

const labels = ['2021-06-07 00:00:00', '2021-06-08 00:00:00', '2021-06-09 00:00:00'];

const data = {
    labels: labels,
    datasets: [{
        label: 'Fixed defects',
        backgroundColor: 'rgb(0, 255, 0)',
        borderColor: 'rgb(0, 255, 0)',
        data: ['4', '10', '23'],
        barThickness: 5
    }, {
        label: 'Open defects',
        backgroundColor: 'rgb(255, 0, 0)',
        borderColor: 'rgb(255, 0, 0)',
        data: ['43', '7', '1'],
        barThickness: 5

    }]
};

const config = {
    type: 'bar',
    data: data,
    options: {
        scales: {
            x: {
                min: '2021-06-07 00:00:00',
                max: '2021-09-10 00:00:00',
                type: 'time',
                time: {
                    unit: 'week'
                },
                stacked: true,
            },
            y: {
                    title: {
                    text: ['Project1', 'Number of defects', 'Project2'],
                  display: true
                },
                stacked: true,
            }
        },
        plugins: {
            customTitle: {
                display: true,
                text: ['Project1', 'Number of defects', 'Project2']
            }
        }
    },
    plugins: [customTitle]
};

const myChart = new Chart(
    document.getElementById('myChart'),
    config
);
<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/chart.js@^3"></script>
<script src="https://cdn.jsdelivr.net/npm/moment@^2"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@^1"></script>

<body>
  <div>
    <canvas height="100px" id="myChart"></canvas>
  </div>
</body>

Here's the fiddle to the same code snippet if you prefer testing it there: Example fiddle

I also tried adding new lines in the custom rotated title like so:

customTitle: {
   display: true,
   text: 'Project1\nNumber of defects\nProject2'
}

But that didn't work either. How can I get both the separate lines title and the correct orientation?

Any help would be appreciated.

CodePudding user response:

If you make the customTitle an array you can make a for loop over the array and make multiple ctx.fillText() calls as follows:

const customTitle = {
    id: 'customTitle',
    beforeLayout: (chart, args, opts) => {
        const {display,font} = opts;
        if (!display) {
            return;
        }
        const {ctx} = chart;
        ctx.font = font || '12px "Helvetica Neue", Helvetica, Arial, sans-serif'
        const {width} = ctx.measureText(opts.text);
        chart.options.layout.padding.left = width * 1.1;
    },
    afterDraw: (chart, args, opts) => {
        const {font,text,color} = opts;
        const {ctx,chartArea: {top,bottom,left,right}} = chart;
        if (opts.display) {
            ctx.fillStyle = color || Chart.defaults.color
            ctx.font = font || '12px "Helvetica Neue", Helvetica, Arial, sans-serif'
            ctx.fillText(text[0], 3, (top   bottom) / 4)
            ctx.fillText(text[1], 3, (top   bottom) / 2)
            ctx.fillText(text[2], 3, (top   bottom) / 1)
        }
    }
}

const labels = ['2021-06-07 00:00:00', '2021-06-08 00:00:00', '2021-06-09 00:00:00'];

const data = {
    labels: labels,
    datasets: [{
        label: 'Fixed defects',
        backgroundColor: 'rgb(0, 255, 0)',
        borderColor: 'rgb(0, 255, 0)',
        data: ['4', '10', '23'],
        barThickness: 5
    }, {
        label: 'Open defects',
        backgroundColor: 'rgb(255, 0, 0)',
        borderColor: 'rgb(255, 0, 0)',
        data: ['43', '7', '1'],
        barThickness: 5

    }]
};

const config = {
    type: 'bar',
    data: data,
    options: {
        scales: {
            x: {
                min: '2021-06-07 00:00:00',
                max: '2021-09-10 00:00:00',
                type: 'time',
                time: {
                    unit: 'week'
                },
                stacked: true,
            },
            y: {
                stacked: true,
            }
        },
        plugins: {
            customTitle: {
                display: true,
                text: ['Project1', 'Number of defects', 'Project2']
            }
        }
    },
    plugins: [customTitle]
};

const myChart = new Chart(
    document.getElementById('myChart'),
    config
);
<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/chart.js@^3"></script>
<script src="https://cdn.jsdelivr.net/npm/moment@^2"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@^1"></script>

<body>
  <div>
    <canvas height="100px" id="myChart"></canvas>
  </div>
</body>

CodePudding user response:

You can add an extra check if it is an array, in which case you first only calculate the padding for the longest element so you dont have a big white space, after that you can calculate the Y starting position and increase it for each element in the array so you can have as many lines as you want

const customTitle = {
  id: 'customTitle',
  beforeLayout: (chart, args, opts) => {
    const {
      display,
      font
    } = opts;
    if (!display) {
      return;
    }
    const {
      ctx
    } = chart;
    ctx.font = font || '12px "Helvetica Neue", Helvetica, Arial, sans-serif'

    let width = 0;

    if (Array.isArray(opts.text)) {
      opts.text.forEach(e => {
        const tmpWidth = ctx.measureText(e).width;
        if (tmpWidth > width) {
          width = tmpWidth;
        }
      });
    } else {
      width = ctx.measureText(opts.text).width;
    }
    chart.options.layout.padding.left = width * 1.1;
  },
  afterDraw: (chart, args, opts) => {
    const {
      font,
      text,
      color
    } = opts;
    const {
      ctx,
      chartArea: {
        top,
        bottom,
        left,
        right
      }
    } = chart;
    if (opts.display) {
      ctx.fillStyle = color || Chart.defaults.color
      ctx.font = font || '12px "Helvetica Neue", Helvetica, Arial, sans-serif'

      if (Array.isArray(text)) {
        const height = ctx.measureText("M").width;
        let y = ((top   bottom) / 2) - Math.ceil(text.length / 2) * height;

        text.forEach(e => {
          ctx.fillText(e, 3, y);
          y  = height   opts.lineSpacing || 0;
        });

      } else {
        ctx.fillText(text, 3, (top   bottom) / 2)
      }
    }
  }
}

const labels = ['2021-06-07 00:00:00', '2021-06-08 00:00:00', '2021-06-09 00:00:00'];

const data = {
  labels: labels,
  datasets: [{
    label: 'Fixed defects',
    backgroundColor: 'rgb(0, 255, 0)',
    borderColor: 'rgb(0, 255, 0)',
    data: ['4', '10', '23'],
    barThickness: 5
  }, {
    label: 'Open defects',
    backgroundColor: 'rgb(255, 0, 0)',
    borderColor: 'rgb(255, 0, 0)',
    data: ['43', '7', '1'],
    barThickness: 5

  }]
};

const config = {
  type: 'bar',
  data: data,
  options: {
    scales: {
      x: {
        min: '2021-06-07 00:00:00',
        max: '2021-09-10 00:00:00',
        type: 'time',
        time: {
          unit: 'week'
        },
        stacked: true,
      },
      y: {
        stacked: true,
      }
    },
    plugins: {
      customTitle: {
        display: true,
        text: ['Project1', 'Number of defects', 'Project2'],
        lineSpacing: 8
      }
    }
  },
  plugins: [customTitle]
};

const myChart = new Chart(
  document.getElementById('myChart'),
  config
);
<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/chart.js@^3"></script>
<script src="https://cdn.jsdelivr.net/npm/moment@^2"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@^1"></script>

<body>
  <div>
    <canvas height="100px" id="myChart"></canvas>
  </div>
</body>

  • Related