ChartJS 2.0 - 饼图上的蜷缩标签

时间:2016-10-08 14:35:51

标签: javascript charts html5-canvas chart.js

我需要绘制包含许多值(其中一部分是接近的)和长值标签的图表。值应显示在图表上(而不是图例)。我使用以下插件显示标签:

var drawItemsValuesPlugin = {
    afterDraw: function(chartInstance) {

        var ctx = chartInstance.chart.ctx;

        // render the value of the chart above the bar
        ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, 'normal', Chart.defaults.global.defaultFontFamily);
        ctx.textAlign    = 'center';
        ctx.textBaseline = 'bottom';        
        ctx.strokeStyle = 'black';
        ctx.font = "bold 11pt Arial";
        ctx.lineWidth = 0.3;

        chartInstance.data.datasets.forEach(function (dataset) {
                for (var i = 0; i < dataset.data.length; i++) {    
                    var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model,
                        total = dataset._meta[Object.keys(dataset._meta)[0]].total,
                        mid_radius = model.innerRadius + (model.outerRadius - model.innerRadius)/2,
                        start_angle = model.startAngle,
                        end_angle = model.endAngle,
                        mid_angle = start_angle + (end_angle - start_angle)/2;

                    var fact = (chartInstance.config.type == 'pie'?
                        (i%2==0?1:1.6):
                        (i%2==0?0.9:1.1)
                    );
                    var x = mid_radius * fact * Math.cos(mid_angle);
                    var y = mid_radius * fact * Math.sin(mid_angle);

                    ctx.fillStyle = '#fff';

                    var label = chartInstance.config.data.labels[i];
                    var percent = String(Math.round(dataset.data[i]/total*100)) + "%";
                    ctx.fillText(label, model.x + x, model.y + y);
                    ctx.strokeText(label, model.x + x, model.y + y);
                    // Display percent in another line, line break doesn't work for fillText
                    ctx.fillText(dataset.data[i] + ' ('+percent+')', model.x + x, model.y + y + 15);
                    ctx.strokeText(dataset.data[i] + ' ('+percent+')', model.x + x, model.y + y + 15);
                }
            });
    }
};

但我面临以下问题:

enter image description here

值标签挤在一起。一个简单的解决方案是将标签绕在轨道图上,如下图所示:

enter image description here

如何使用Chart JS 2.0获得此标签位置?

3 个答案:

答案 0 :(得分:0)

注意:下面不是工作代码。只是一个布局。

x,y = 0,0 = middle of pie chart / middle of the circle

//这4个点=甜甜圈派的一部分。

xa[1],ya[1] = x,y point on the outside of circle.
xb[1],yb[1] = x,y point on the outside of circle.
xc[1],yc[1] = x,y point on the inside of circle.
xd[1],yd[1] = x,y point on the inside of circle.

//仅查找外圈的中间坐标

xab[1],yab[1] = x,y point on the outside circle, between xa,ya and xb,yb

//您需要找到以像素为单位的文字高度。

function somefunc(){
  //i do know how to do this. 
}

//这是文本的一半高度,因此可以将线条绘制到文本的中点。

var label_height_pixel[1] = //no idea how to figure this out
var find_middle_of_text[1] = label_height_pixel[1] / 2;

//制作一个更大的圆圈,然后添加一个小填充,使文字在圆圈上不正确。

//好的坏代码弄清楚x,y坐标是什么。使用

xab[1],yab[1] and x,y
var xtext[1],ytext[1] = outer_circle_radius + find_middle_of_text[1] + padding

对上面的所有内容进行for循环。 更改上面的最后一个代码。这样它每增加一点就会增加。所以没有标签会在彼此之上运行。

my_next_height[2] = outer_circle_radius + label_height_pixel[1] + find_middle_of_text[2] + padding
my_next_height[3] = outer_circle_radius + label_height_pixel[1] + label_height_pixel[2] + find_middle_of_text[3] + padding
my_next_height[4] = outer_circle_radius + label_height_pixel[1] + label_height_pixel[2] + label_height_pixel[3] + find_middle_of_text[3] + padding

你需要输入一个if / then / else函数来检查x是正数还是负数。并从my_next_height []向左或向右绘制线条。

然后在你的文字中画画。

还有一点是从外圆绘制线,然后排到目前为止,然后放置标签。但是上面切断了代码的布局,应该给你足够的坐标来实现这一点。

我尽量避免使用x,y和z坐标。在没有打开旧的小学数学书的情况下,我永远无法记住找到点和角度的数学。并查看你的代码mid_radius,以及所有代码,我没有看到我在找到正确的x,y绘图点时记得的正常数学。

答案 1 :(得分:0)

试试这个,如果可以帮助你,作为charjs

的简单解决方案
$.plot('#placeholder', data, {
    series: {
        pie: {
            show: true
        }
    },
    legend: {
        show: false
    }
});

答案 2 :(得分:0)

这是答案草案。它的主要目的是给你希望。没有办法是最终的(它将在几天内完成),因为:

  • 代码丑陋,黑客攻击,无证件。它基于您提供的插件。
  • 该代码需要Chart.js 2的下一个次要版本(可能是2.3.1)。我已经构建了今天Chart.js的主分支并将其上传到我的Dropbox中,因为最近合并的layout.padding选项是必需的,以便为围绕轨道标签提供空间(没有此选项)标签会被切断)。
  • 绝对需要改进。

这是demo

一条小线将饼图的每个切片连接到轨道上的相应标签。连续标签(除了第一个和最后一个,如果数据集具有奇数个元素)将以不同的半径进行轨道运行,因此可以避免冲突。但我猜需要更多关心。主要问题是如何确保图表具有响应性。 layout.padding是指像素,而不是百分比。此外,标签字体目前在大小方面是固定的。我认为只是将标签的位置设置为距画布边界一定距离(因为填充也是固定的)可以解决这个问题(调整图表大小会给出一些想法,但稍后会有更多的想法)。

下面还提供了演示,但是它无法从Dropbox加载JavaScript文件(而JSFiddle不会失败 - 当更新Chart.js的CDN版本时,我将更新所有链接):

&#13;
&#13;
var drawItemsValuesPlugin = {
  afterDraw: function(chartInstance) {

    var ctx = chartInstance.chart.ctx;

    // render the value of the chart above the bar
    ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, 'normal', Chart.defaults.global.defaultFontFamily);
    ctx.textAlign = 'center';
    ctx.textBaseline = 'bottom';
    ctx.strokeStyle = 'black';
    ctx.font = "bold 11pt Arial";
    ctx.lineWidth = 0.3;

    chartInstance.data.datasets.forEach(function(dataset) {
      for (var i = 0; i < dataset.data.length; i++) {
        var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model,
          total = dataset._meta[Object.keys(dataset._meta)[0]].total,
          mid_radius = model.innerRadius + (2.4 * model.outerRadius - model.innerRadius) / 2,
          start_angle = model.startAngle,
          end_angle = model.endAngle,
          mid_angle = start_angle + (end_angle - start_angle) / 2;

        var mid_radius2 = 0.92 * (model.innerRadius + (2.4 * model.outerRadius - model.innerRadius) / 2);

        var fact = i % 2 == 0 ? 1.00 : 1.15;

        var x = mid_radius * fact * Math.cos(mid_angle);
        var y = mid_radius * fact * Math.sin(mid_angle);

        var myX = mid_radius2 * fact * Math.cos(mid_angle);
        var myY = mid_radius2 * fact * Math.sin(mid_angle);

        var myX2 = 0.91 * mid_radius2 * 1 * Math.cos(mid_angle);
        var myY2 = 0.91 * mid_radius2 * 1 * Math.sin(mid_angle);

        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.moveTo(model.x + myX2, model.y + myY2);
        ctx.strokeStyle = '#232323';
        ctx.lineTo(model.x + myX, model.y + myY);
        ctx.stroke();
        ctx.strokeStyle = 'black';
        ctx.fillStyle = '#454545';
        ctx.lineWidth = 0.3;

        var label = chartInstance.config.data.labels[i];
        var percent = String(Math.round(dataset.data[i] / total * 100)) + "%";
        ctx.fillText(label, model.x + x, model.y + y);
        ctx.strokeText(label, model.x + x, model.y + y);
        // Display percent in another line, line break doesn't work for fillText
        ctx.fillText(dataset.data[i] + ' (' + percent + ')', model.x + x, model.y + y + 15);
        ctx.strokeText(dataset.data[i] + ' (' + percent + ')', model.x + x, model.y + y + 15);
      }
    });
  }
};

Chart.pluginService.register(drawItemsValuesPlugin);

var ctx = document.getElementById("myChart");

var myChart = new Chart(ctx, {
  type: 'pie',
  data: {
    labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
    datasets: [{
      data: [15, 1, 1, 1, 45, 1],
      backgroundColor: [
        'rgba(255, 99, 132, 0.2)',
        'rgba(54, 162, 235, 0.2)',
        'rgba(255, 206, 86, 0.2)',
        'rgba(75, 192, 192, 0.2)',
        'rgba(153, 102, 255, 0.2)',
        'rgba(255, 159, 64, 0.2)'
      ],
      borderColor: [
        'rgba(255,99,132,1)',
        'rgba(54, 162, 235, 1)',
        'rgba(255, 206, 86, 1)',
        'rgba(75, 192, 192, 1)',
        'rgba(153, 102, 255, 1)',
        'rgba(255, 159, 64, 1)'
      ],
      borderWidth: 1
    }]
  },
  options: {
    legend: {
      display: false
    },
    layout: {
      padding: 140
    }
  }
});
&#13;
<script src="https://dl.dropboxusercontent.com/s/yju3s0dqe32um2k/Chart.min.js"></script>
<canvas id="myChart" width="400" height="400"></canvas>
&#13;
&#13;
&#13;