【问题标题】:Grouping y-axis labels on multiple lines on a ChartJS with an extra dimension在具有额外维度的 ChartJS 上对多行上的 y 轴标签进行分组
【发布时间】:2021-07-20 08:50:13
【问题描述】:

我有这个关于 SO (Using ChartJS to create a multiple grouped bar chart - see picture below) 的答案,能够成功地使 y 轴上的第二行标签正确且动态地工作。

我想知道是否有办法让它们在多行上运行?请参阅下图中的标签:

由于它是动态构建的图表并且响应速度非常快,因此有时我的数据比允许的空间长。

因此,低于 2.50 有时可能会太长,以至于超出下一个(总计)框。例如,如果低于 2.50 类似于“快速棕色狐狸跳过懒狗一堆并跑到总列”,那么它会重叠。

我需要想办法解决这个问题。

【问题讨论】:

    标签: typescript charts chart.js data-visualization aurelia


    【解决方案1】:

    TL;DR

    查看此答案的最底部(有两种方法,选择第二种):最简单的方法是使用字符串数组(string []) 用于数据端的各个类别,并使用单独的@打印每个数组项987654326@印刷方电话。

    代码部分 #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 函数添加了行数计算,并使用它来生成正确的空间量。

    上面的内容在使用该字体的两个衬里时效果很好。例如,一句“The quick brown fox jumps over the lazy dog”就可以了。然后成为图表中空间的限制:如果内容进入第三行,则图例将从底部剪切,因为它不再适合画布。为此,我尝试了以下更改:

      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 部分的最后说明:

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

    编辑:更多'Chart.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>

    解释,第 2 部分

    所以,所有标签、标题等都有两个选项(例如 [3] 上的那个):

    1. 字符串

    2. 字符串[]

    从这些选项 1 都在一行中,你猜怎么着:在数组中你可以逐行定义它。一项是内部数组中的一行!

    在您的示例中,这次您自己打印,因此您只需引用字符串数组并定义所引用内容的写入位置!

    我没有采用较小的字体,而是采用一种方法将内容稍微放在页面上,并且示例文本非常适合!

    来源:

    [1]https://www.w3schools.com/tags/playcanvas.asp?filename=playcanvas_filltextmaxwidth

    [2]HTML5 canvas ctx.fillText won't do line breaks?

    [3]https://www.chartjs.org/docs/latest/axes/labelling.html

    【讨论】:

    • 这太棒了。感谢所有 Chart.JS 的帮助。
    • 缺点是我需要弄清楚如何在文本随机时使这种动态适应。
    • 方法一是最有活力的。我没有测试的是像方法 2 中那样取消填充。这样,普通字体也会有足够的空间吗?
    • 显然我用 webpack 项目构建的 VS 代码没有包含 wrapText。
    • 我有这个:stackoverflow.com/q/67540432/665557 我不确定如何调整它。调查一下。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-27
    • 2013-08-12
    • 2012-06-02
    • 2021-01-22
    • 1970-01-01
    相关资源
    最近更新 更多