2

So I'm looking to create a 3d pie chart (in Highcharts), where the depth of each slice is determined by the point's value. The question here should give you a good idea of what I'm going for.

Currently Highcharts only supports depth arguments at the series level, so my first idea was to make multiple series, as is shown here: #1.

1:

Highcharts.chart('container', {
  chart: {
    type: 'pie',
    options3d: {
      enabled: true,
      alpha: 60
    }
  },
  title: {
    text: 'Contents of Highsoft\'s weekly fruit delivery'
  },
  subtitle: {
    text: '3D donut in Highcharts'
  },
  plotOptions: {
    pie: {
      innerSize: 125,
      depth: 60
    }
  },
  series: [{
    name: 'Delivered amount',
    data: [
      { name: 'a', y: 1, val: 0.59, color: '#25683d' },
      { name: 'b', y: 1, val: 1.25, color: '#222f58' },
      { name: 'c', y: 2, val: 0.73, color: '#bebe89' },
      
    ],
    depth: 45,
    startAngle: 0,
    endAngle: 180
  }, {
    data: [
      { name: 'd', y: 2, val: -0.69, color: '#900' },
      { name: 'e', y: 2, val: 0.57, color: '#a0a0a0' }
    ],
  depth: 60,
    startAngle: 180,
    endAngle: 360
  }]
});
<script src="https://code.highcharts.com/highcharts.js">
</script>
<script src="https://code.highcharts.com/highcharts-3d.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
<script src="https://code.highcharts.com/modules/export-data.js"></script>

<div id="container" style="height: 400px"></div>
  • The issue with this being that the depth appears to keep the same top reference point, instead of the intended results wherein each 'slice' starts at the same y point.

When that didnt work, I went back to storing all data in one series, and trying to update the SVG elements depth after render (as can be seen here: #2.

2:

Highcharts.chart('container', {
  chart: {
    type: 'pie',
    options3d: {
      enabled: true,
      alpha: 60
    },
    events: {
      load: function() {
        this.series[0].data.forEach((val, i) => {
          var depth = val.graphic.attribs.depth;
          val.graphic.attribs.depth = depth + (i * 10);
        });
        this.redraw();
      }
    }
  },
  title: {
    text: 'Contents of Highsoft\'s weekly fruit delivery'
  },
  subtitle: {
    text: '3D donut in Highcharts'
  },
  plotOptions: {
    pie: {
      innerSize: 125,
      depth: 60
    }
  },
  series: [{
    name: 'Delivered amount',
    data: [{
        name: 'a',
        y: 1,
        val: 0.59,
        color: '#25683d'
      },
      {
        name: 'b',
        y: 1,
        val: 1.25,
        color: '#222f58'
      },
      {
        name: 'c',
        y: 2,
        val: 0.73,
        color: '#bebe89'
      },
      {
        name: 'd',
        y: 2,
        val: -0.69,
        color: '#900'
      },
      {
        name: 'e',
        y: 2,
        val: 0.57,
        color: '#a0a0a0'
      }

    ],

  }]
});
  • Once again, the depth appears to just add content down, and my attempts at translation (https://api.highcharts.com/class-reference/Highcharts.SVGElement#translate) to shift each slice up have been less than successful in maintaining an aesthetically pleasing chart.. for instance: #3.

    • also worth noting that i had to define an array for the y translations, which was mostly the results of trial and error

3:

Highcharts.chart('container', {
  chart: {
    type: 'pie',
    options3d: {
      enabled: true,
      alpha: 60
    },
    events: {
      load: function() {
        this.series[0].data.forEach((val, i) => {
          var depth = val.graphic.attribs.depth;
          val.graphic.attribs.depth = depth + (i * 10);
        });
        this.redraw();
      },
      redraw: function() {
        var y_trans = [0, -8, -16, -25, -34];
        this.series[0].data.forEach((val, i) => {
          val.graphic.translate(0, y_trans[i]);
        });
      }
    }
  },
  title: {
    text: 'Contents of Highsoft\'s weekly fruit delivery'
  },
  subtitle: {
    text: '3D donut in Highcharts'
  },
  plotOptions: {
    pie: {
      innerSize: 125,
      depth: 60
    }
  },
  series: [{
    name: 'Delivered amount',
    data: [{
        name: 'a',
        y: 1,
        val: 0.59,
        color: '#25683d'
      },
      {
        name: 'b',
        y: 1,
        val: 1.25,
        color: '#222f58'
      },
      {
        name: 'c',
        y: 2,
        val: 0.73,
        color: '#bebe89'
      },
      {
        name: 'd',
        y: 2,
        val: -0.69,
        color: '#900'
      },
      {
        name: 'e',
        y: 2,
        val: 0.57,
        color: '#a0a0a0'
      }

    ],

  }]
});

Any references or ideas would be greatly appreciated as I'm starting to hit a wall.

laventnc
  • 175
  • 13

1 Answers1

3

You can use Highcharts wrap and extend translate method:

var each = Highcharts.each,
    round = Math.round,
    cos = Math.cos,
    sin = Math.sin,
    deg2rad = Math.deg2rad;

Highcharts.wrap(Highcharts.seriesTypes.pie.prototype, 'translate', function(proceed) {
    proceed.apply(this, [].slice.call(arguments, 1));

    // Do not do this if the chart is not 3D
    if (!this.chart.is3d()) {
        return;
    }

    var series = this,
        chart = series.chart,
        options = chart.options,
        seriesOptions = series.options,
        depth = seriesOptions.depth || 0,
        options3d = options.chart.options3d,
        alpha = options3d.alpha,
        beta = options3d.beta,
        z = seriesOptions.stacking ? (seriesOptions.stack || 0) * depth : series._i * depth;

    z += depth / 2;

    if (seriesOptions.grouping !== false) {
        z = 0;
    }

    each(series.data, function(point) {

        var shapeArgs = point.shapeArgs,
            angle;

        point.shapeType = 'arc3d';

        var ran = point.options.h;

        shapeArgs.z = z;
        shapeArgs.depth = depth * 0.75 + ran;
        shapeArgs.alpha = alpha;
        shapeArgs.beta = beta;
        shapeArgs.center = series.center;
        shapeArgs.ran = ran;

        angle = (shapeArgs.end + shapeArgs.start) / 2;

        point.slicedTranslation = {
            translateX: round(cos(angle) * seriesOptions.slicedOffset * cos(alpha * deg2rad)),
            translateY: round(sin(angle) * seriesOptions.slicedOffset * cos(alpha * deg2rad))
        };
    });
});

(function(H) {
    H.wrap(Highcharts.SVGRenderer.prototype, 'arc3dPath', function(proceed) {
        // Run original proceed method
        var ret = proceed.apply(this, [].slice.call(arguments, 1));
        ret.zTop = (ret.zOut + 0.5) / 100;
        return ret;
    });
}(Highcharts));

Also, in load event you need to reverse the chart:

        events: {
            load: function() {
                var each = Highcharts.each,
                    points = this.series[0].points;

                each(points, function(p, i) {
                    p.graphic.attr({
                        translateY: -p.shapeArgs.ran
                    });

                    p.graphic.side1.attr({
                        translateY: -p.shapeArgs.ran
                    });

                    p.graphic.side2.attr({
                        translateY: -p.shapeArgs.ran
                    });
                });
            }
        }

Live demo: http://jsfiddle.net/BlackLabel/c4wk5z9L/

Docs: https://www.highcharts.com/docs/extending-highcharts

ppotaczek
  • 36,341
  • 2
  • 14
  • 24
  • 1
    one small thing i noticed... deg2rad should be Highcharts.deg2rad not Math.deg2rad. Other than that, perfect ! Thanks again – laventnc Feb 12 '19 at 15:24
  • One other thing if you can help... Im trying to add a feature for negative values. I keep my mapping function (from true value to depth) positive, and if the true value is negative I simply reverse the translateY arguments to shift the slice 'down'. however, this leads to needing to shift the center of these negative slices 'back' into the screen so that the edges align. Any ideas? – laventnc Feb 12 '19 at 16:26
  • 2
    Hi laventnc, It looks like it's enough to set fixed `translateY` value: http://jsfiddle.net/BlackLabel/073o1wum/ – ppotaczek Feb 12 '19 at 16:40
  • 1
    incase anyone else is helped out by this, in order to dynamically set the fixed translateY value, I found that setting it to the difference between the heights of the smallest negative slice and smallest positive slice yielded good results – laventnc Feb 12 '19 at 17:21