Recently I wanted to center a sprite (image or text) inside an Extjs donut chart (Sencha Charts, Extjs 5.1+).  There's not an out-of-the-box way to do this in Extjs and I couldn't find much online, so I figured out a solution and now I'm sharing it.  I was using this to display a project logo image in the center of the donut, however the same principal applies to display any sprite to include textual data.

You'll need to do two things, which require knowing the image size, the chart's container size, and some simple math.

  1. Draw the sprite and center it inside the donut using the chart's boxready listener.
  2. Move the sprite to the donut's center using the chart's resizeHandler.

Please note you'll need to account for things like insetPadding and legend height/width depending where/if you have a legend displayed.  Please see the code snippet below for implementation details on the boxready listener and resizeHandler.


Ext.define('My.pie.chart', {
  extend: 'Ext.panel.Panel',

  initComponent: function() {
    this.items: [{
      xtype: 'polar',
      reference: 'chart',
      width: '100%',
      height: '100%',
      insetPadding: 50,
      // ... other chart config here
      legend: {
        field: 'someField',
        position: 'bottom'
      },
      series: [{
        type: 'pie',
        donut: 50,
        angleField: 'sliceSize',
        // ... other pie config here
      ]},
      resizeHandler: function(size) {
        this.scheduleLayout();

        var surface = this.getSurface();
        // we're only dealing with a single sprite here, but there are better ways to find a particular sprite on the surface if need be.
        var sprite = surface.getItems()[0];
        if (sprite) {
          // Note: when moving the sprite, we need to account for insetPadding, imageSize, and legend height.  This assumes the legend is on the top or bottom.  If the legend is on the side, use the inset padding from left/right instead
          var imageSize = 150;  // this could be a property on the sprite
          var insetPadding = this.insetPadding.top;
          var legendHeight = 0;
          if (this.getLegend()) {
            legendHeight = this.getLegend().getHeight();
          }
          sprite.setAttributes({
            // account for insetPadding when finding hte center width
            x: ((size.width/2 - insetPadding) - imageSize/2),
            // account for legend height and insetPadding when finding the center height
            y: (((size.height - legendHeight)/2 - insetPadding) - imageSize/2)
          });
        }
      },
      listeners: {
        boxready: function(me) {
          // Defer by the same amount as the chart animation (if there is any) so we know the chart is in it's final resting location
          Ext.defer(function() {
            var imageSize = 150; // this could be a property on the sprite
            var surface = this.getSurface();
            var sprite = surface.add({
            type: 'image',
            itemId; 'mySpriteImage',
            src: 'mySprite.png',
            width: imageSize,
            height: imageSize,
            // subtract half the image width from half the chart width to get the center
            x: (surface.getRect()[2]/2 - imageSize/2),
            // subtract half the image height from half the chart height to get the center
            y: (surface.getRect()[3]/2 - imageSize/2),
            surface: surface
          }, animationDelay);
        }
      }
    }];
    this.callParent(arguments);
  }
});

Comment