chart.js 如何在单独的行上显示多个 y 轴标题并顺时针旋转 90 度

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

编辑

我的目标是在 chartjs 中顺时针旋转 90 度后在不同的行上显示多个 y 轴标题。

我希望将绿色圆圈标题旋转到红色圆圈标题的方向,同时保持 multi-line 标题

多个标题的默认方向是绿色圆圈中显示的方向。

我希望标题在不同的行上(如绿色圆圈)并顺时针旋转90度(如红色圆圈的方向)。

为了在不同的行上获取标题,我实际上创建了一个字符串数组,如下所示:Add multiple lines as Y axis title in chartJs

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

以上代码在不同的行中给出了正确的标题,但方向不正确。

将标题顺时针旋转 90 度的一种解决方案是添加自定义标题常量并将其作为插件添加到配置中,如下所示:

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>

上面的代码片段给出了正确的方向,但没有在单独的行中显示标题。

然而,当我尝试将两种解决方案组合在一起时,我得到:

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>

这里是相同代码片段的 fiddle,如果您更喜欢在此处进行测试:Example fiddle

我还尝试在自定义旋转标题中添加新行,如下所示:

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

但这也没有用。如何获得 单独的行标题和正确的方向?

如有任何帮助,我们将不胜感激。

如果是数组的话可以加一个额外的检查,这种情况下先只计算最长元素的padding,这样就不会有大白了space,之后就可以计算Y了起始位置并为数组中的每个元素增加它,这样你就可以拥有任意多行

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>