Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Highcharts Multi-Depth 3d pie chart

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.

like image 447
laventnc Avatar asked Jan 20 '26 13:01

laventnc


1 Answers

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

like image 199
ppotaczek Avatar answered Jan 23 '26 04:01

ppotaczek