Highcharts datalabel 'callout' shape not w

2019-08-04 02:06发布

问题:

I've been trying to implement dynamic 'callout' shape datalabels for donut chart using Highcharts javascript library, where notch of datalabels point to respective arc. something like this: https://imgur.com/yKhWKOu

I've created the 'callout' shape using highcharts renderer method, but unable to make it dynamic. This is what I'm getting right now: https://imgur.com/VMuVwdk My code is :

<!DOCTYPE html>
<html>
<head>
</head>
<body>
  <div id="container" style="min-width: 310px; height: 400px; max-width: 600px; margin: 0 auto"></div>


  <script type="text/javascript" src="https://code.highcharts.com/highcharts.js"></script>

<script type="text/javascript">
  (function(Highcharts) {
    Highcharts.Renderer.prototype.symbols.callout = function(x, y, w, h) {
      var arrowLength = 6,
        halfDistance = 6,
        r = Math.min(0, w, h),
        safeDistance = r + halfDistance,
        <!-- anchorX = options && options.anchorX,
        //anchorY = options && options.anchorY, 
        path;
      path = [
        'M', x + r, y,      // 
        'L', x + w - r, y, // top side  
        'C', x + w, y, x + w, y, x + w, y + r, // top-right corner  
        'L', x + w, y + h - r, // right side 
        'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-right corner 
        'L', x + r, y + h, // bottom side 
        'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner 
        'L', x, y + r, // left side 
        'C', x, y, x, y, x + r, y // top-right corner
      ];
      path.splice(23, 
        3,
        'L',
        w / 2 + halfDistance,
        y + h,
        w / 2, 
        y + h + arrowLength,
        w / 2 - halfDistance, 
        y + h,
        x + r, 
        y + h
      );
      return path;
    };
  }(Highcharts));

  Highcharts.chart('container', {
    plotOptions: {
      pie: {
        dataLabels: {
          enabled: true,
          style: {
            fontWeight: 'bold',
            color: 'white'
          },
          connectorWidth: 0,
          distance: 10, 
          shape: 'callout',
          backgroundColor: 'red',
          <!-- backgroundColor: 'rgba(0, 0, 0, 0.75)', -->
          style: {
            color: '#FFFFFF',
            textOutline: 'none'
          }
        },
        startAngle: 0,
        endAngle: 360,
        center: ['50%', '50%']
      }
    },
    series: [{
      type: 'pie',
      innerSize: '80%',
      data: [
        ['Firefox', 10.38],
        ['IE', 56.33],
        ['Chrome', 24.03],
        ['Opera', 31.44]
      ]
    }]
  }, function(chart) {

  });
  </script>
  </body>

  </html>

'Thanks in advance'

回答1:

I examined this subject and it seems that it's rather tricky thing to do in Highcharts.

The library uses SVGRenderer.label (https://api.highcharts.com/class-reference/Highcharts.SVGRenderer#label) function for creating the data labels.

Chevron (small arrow) appears in callout shape label only when xAnchor and yAnchor are definded and necessary conditions from the Highcharts.Renderer.prototype.symbols.callout function are satisfied (https://github.com/highcharts/highcharts/blob/master/js/parts/SvgRenderer.js). When data labels are generated for pie series these values are not defined - chevron doesn't appear.

You have overwritten this function and hardcoded the label path - that's not the solution when you want it to be responsive.

Here's a workaround that I found:

I set dataLabels.format to empty string - the label are generated, but they're not visible, because they have no content. I use their position in chart.events.render to generate new labels:

  render: function() {
    var chart = this,
      series = chart.series[0],
      renderer = chart.renderer;

    series.points.forEach(function(p) {
      var dl = p.dataLabel,
        x = dl.x + chart.plotLeft,
        y = dl.y + chart.plotTop,
        chartCenterX = chart.chartWidth / 2,
        chartCenterY = chart.chartHeight / 2,
        anchorX,
        anchorY;

      // destroy the old label
      if (dl.customLabel) {
        dl.customLabel.destroy();
      }

      // definitions for all the directions
      if (x < chartCenterX && y < chartCenterY) { // right bottom corner chevron
        anchorX = x + 30;
        anchorY = y + 50;
      } // more should be added here

      // create custom label
      dl.customLabel = renderer.label(p.name, x, y, 'callout', anchorX, anchorY).attr({
        fill: 'blue'
      }).css({
        color: 'white'
      }).add();

    });
  }

Live demo: http://jsfiddle.net/kkulig/8s968m7f/

The biggest challenge here is to find a proper coordinates for anchors. I only did that for the label(s) in the top-left part of the plot area (it's just for example purposes - better formulas need to be found). I used the dimensions of the chart to compute these values.

It's not very elegant solution, but if you find proper formulas and do some coding it'll work.