在ChartJS上使用额外维度对多行上的y轴标签进行分组

svgewumm  于 2023-04-30  发布在  Chart.js
关注(0)|答案(1)|浏览(143)

我在SO(Using ChartJS to create a multiple grouped bar chart - see picture below)上的这个答案能够成功地使y轴上的第二行标签正确和动态地工作。
我想知道是否有一种方法可以让它们在多条线上运行?请参见下图中的标签:

由于它是一个动态构建的图表,并且响应速度非常快,所以有时我的数据长度超过了允许的空间。
低于2。50有时可能太长,以至于超出到下一个(总计)框。例如,如果小于2。50是类似于“快速的棕色狐狸跳过懒惰的狗一堆次,跑进总列”,那么它就会重叠。
我得想办法解决这事。

gc0ot86w

gc0ot86w1#

TL;DR

看看这个答案的最下面(有两种方式,选择第二种):最简单的方法是在数据端使用字符串数组(string [])用于各个类别,并在打印端使用单独的ctx.fillText()调用打印每个数组项。

代码部分#1:

CanvasRenderingContext2D.prototype.wrapText = function(text, x, y, maxWidth, lineHeight) {

  var lines = text.split("\n");
  var linesCount = 0;
  for (var i = 0; i < lines.length; i++) {

    var words = lines[i].split(' ');
    var line = '';

    for (var n = 0; n < words.length; n++) {
      var testLine = line + words[n] + ' ';
      var metrics = this.measureText(testLine);
      var testWidth = metrics.width;
      if (testWidth > maxWidth && n > 0) {
        this.fillText(line, x, y);
        linesCount += 1;
        line = words[n] + ' ';
        y += lineHeight;
      } else {
        line = testLine;
      }
    }

    this.fillText(line, x, y);
    linesCount += 1;
    y += lineHeight;
  }
  return linesCount;
}
new Chart('myChart', {
  type: 'bar',
  plugins: [{
    afterDraw: chart => {
      let ctx = chart.chart.ctx;
      ctx.save();
      let xAxis = chart.scales['x-axis-0'];
      let xCenter = (xAxis.left + xAxis.right) / 2;
      let yBottom = chart.scales['y-axis-0'].bottom;
      ctx.textAlign = 'center';
      ctx.font = '12px Arial';
      var size1 = ctx.wrapText(chart.data.categories[0], (xAxis.left + xCenter) / 2, yBottom + 40, 160, 16);
      var size2 = ctx.wrapText(chart.data.categories[1], (xCenter + xAxis.right) / 2, yBottom + 40, 160, 16);
      var size = size1 > size2 ? size1 : size2;
      chart.options.legend.labels.padding = size * 16 + 5;
      ctx.strokeStyle = 'lightgray';
      [xAxis.left, xCenter, xAxis.right].forEach(x => {
        ctx.beginPath();
        ctx.moveTo(x, yBottom);
        ctx.lineTo(x, yBottom + 40);
        ctx.stroke();
      });
      ctx.restore();
    }
  }],
  data: {
    labels: ['2004', '2008', '2012', '2016', '2004', '2008', '2012', '2016'],
    categories: ['The quick brown fox jumps over the lazy dog', 'Lower than 2.50'],
    datasets: [{
        label: 'Male',
        data: [42.4, 43.0, 43.0, 50.3, 49.4, 48.4, 51.2, 51.8],
        backgroundColor: 'rgba(124, 181, 236, 0.9)',
        borderColor: 'rgb(124, 181, 236)',
        borderWidth: 1
      },
      {
        label: 'Female',
        data: [57.6, 57.0, 57.0, 49.7, 50.6, 51.6, 53.7, 54.6],
        backgroundColor: 'rgba(67, 67, 72, 0.9)',
        borderColor: 'rgb(67, 67, 72)',
        borderWidth: 1
      }
    ]
  },
  options: {
    legend: {
      position: 'bottom',
      labels: {
        padding: 30,
        usePointStyle: true
      }
    },
    scales: {
      yAxes: [{
        ticks: {
          min: 0,
          max: 80,
          stepSize: 20
        },
        scaleLabel: {
          display: true,
          labelString: 'Percent (%)'
        }
      }],
      xAxes: [{
        gridLines: {
          drawOnChartArea: false
        }
      }]
    }
  }
});
canvas {
  max-width: 400px;
}
<!DOCTYPE html>
<html>

<body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
  <canvas id="myChart" height="200"></canvas>
</body>

</html>

说明#1:

我修复的第一件事是解决,为什么较低的x轴上的图表标题没有写入两行。在连接的溶液中,使用fillText。这是一种围绕canvas元素的方法,但它有一个限制,你不能在没有工作的情况下在几行上完成它。不过,它可以收缩以适应空间[1],但很快文本看起来很糟糕。所以,StackOveflow,找到一个变通方法[2]。有几个解决方案,没有一个简单,但我发现wrapText方法最方便。它使文字只要我想和所有来很好地排队。
第二个问题是,我必须向下调整图例,以避免多个内容在同一空间重叠。因此,我在wrapText函数中添加了一个行的计算,并使用它来生成正确的空间量。
上面的工程罚款与一个两行的字体使用。例如,一个句子“快速棕色狐狸跳过懒惰的狗”就可以很好地工作。然后成为限制的空间在图表中发生:如果内容转到第三行,则从底部剪切图例,因为它不再适合画布。为此,我尝试了以下更改:

ctx.font = '8px Arial';
  var size1 = ctx.wrapText(chart.data.categories[0], (xAxis.left + xCenter) / 2, yBottom + 40, 160, 8);
  var size2 = ctx.wrapText(chart.data.categories[1], (xCenter + xAxis.right) / 2, yBottom + 40, 160, 8);
  var size = size1 > size2 ? size1 : size2;
  chart.options.legend.labels.padding = size * 8 + 8;

我只是把字体大小调整到8个像素。它显示了所有的数据(你的文本也),但看起来不那么漂亮。

第1部分最后说明:

这个解决方案是一个变通方案(插件解决方案)。最长文本的最终解决方案看起来很尴尬,我相信在某个地方有一个更好的方法,但由于我可以让它工作到目前为止,我决定分享我的试验,以防没有其他解决方案不可能,所以你至少有这个。

编辑:更多图表。js’方式,方便:

代码部分#2

new Chart('myChart', {
  type: 'bar',
  plugins: [{
    beforeDraw: chart => {
      let ctx = chart.chart.ctx;
      ctx.save();    
      let xAxis = chart.scales['x-axis-0'];
      let xCenter = (xAxis.left + xAxis.right) / 2;
      let yBottom = chart.scales['y-axis-0'].bottom;
      ctx.textAlign = 'center';
      ctx.font = '12px Arial';
      ctx.fillText(chart.data.categories[0][0], (xAxis.left + xCenter) / 2, yBottom + 30);
      ctx.fillText(chart.data.categories[0][1], (xAxis.left + xCenter) / 2, yBottom + 40);
      ctx.fillText(chart.data.categories[0][2], (xAxis.left + xCenter) / 2, yBottom + 50);
      ctx.fillText(chart.data.categories[1], (xCenter + xAxis.right) / 2, yBottom + 40);
      ctx.strokeStyle  = 'lightgray';
      [xAxis.left, xCenter, xAxis.right].forEach(x => {
        ctx.beginPath();
        ctx.moveTo(x, yBottom);
        ctx.lineTo(x, yBottom + 40);
        ctx.stroke();
      });
      ctx.restore();
    }
  }],
  data: {
    labels: ['2004', '2008', '2012', '2016', '2004', '2008', '2012', '2016'],
    categories: [['The quick brown fox jumps over', 'the lazy dog a bunch of times','and ran into the Total column'], ['Lower than 2.50']],
    datasets: [{
        label: 'Male',
        data: [42.4, 43.0, 43.0, 50.3, 49.4, 48.4, 51.2, 51.8],
        backgroundColor: 'rgba(124, 181, 236, 0.9)',
        borderColor: 'rgb(124, 181, 236)',
        borderWidth: 1
      },
      {
        label: 'Female',
        data: [57.6, 57.0, 57.0, 49.7, 50.6, 51.6, 53.7, 54.6],
        backgroundColor: 'rgba(67, 67, 72, 0.9)',
        borderColor: 'rgb(67, 67, 72)',
        borderWidth: 1
      }
    ]
  },
  options: {
    legend: {
      position: 'bottom',
      labels: {
        padding: 30,
    align: 'end',
        usePointStyle: true
      }
    },
    scales: {
      yAxes: [{
        ticks: {
          min: 0,
          max: 80,
          stepSize: 20
        },
        scaleLabel: {
          display: true,
          labelString: 'Percent (%)'
        }
      }],
      xAxes: [{
        gridLines: {
          drawOnChartArea: false
        }
      }]
    }
  }
});
canvas {
  max-width: 400px;
}
<!DOCTYPE html>
<html>
<head>
    <script src="/scripts/snippet-javascript-console.min.js?v=1"></script>
</head>
<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<canvas id="myChart" height="200"></canvas>
</body>

说明,第二部分

因此,所有标签、标题等都有两个选项(例如[3]上的选项):
1.弦

  1. string[]
    从这些选项中,选项1都在一行中,猜猜看:在数组中,可以按行定义它。一个项目是内部数组中的一行!
    在您的示例中,您自己打印了这个时间,因此您只需引用字符串数组并定义引用的内容写入的位置!
    我采取了一种方法,而不是更小的字体,以采取的内容一点点的网页和例子文本适合刚刚完美!!

来源:

[1][https://www.w3schools.com/tags/playcanvas.asp?filename=playcanvas_filltextmaxwidth](https://www.w3schools.com/tags/playcanvas.asp?filename=playcanvas_filltextmaxwidth)
[2][HTML5 canvas ctx.fillText won't do line breaks?](https://stackoverflow.com/questions/5026961/html5-canvas-ctx-filltext-wont-do-line-breaks)
[3][https://www.chartjs.org/docs/latest/axes/labelling.html](https://www.chartjs.org/docs/latest/axes/labelling.html)

相关问题