Home > Mobile >  Highcharts - Getting a 3d effect with selected pie chart slices
Highcharts - Getting a 3d effect with selected pie chart slices

Time:01-12

In highcharts, I'm trying to make so that when the user selects or hover over a slice of a pie chart, the slice makes the effect of getting up in the z axis (towards the user). I'm trying to accomplish this by setting a shadow filter through css and making the border of the slice wider (with the same color of the fill). However, the issue I face is that the slices can still be below other slices, so the selected slice and its shadow will go behind those slices thus still seeming to be under them. To further illustrate:

Since red was added last, it looks good when selected - it's above the other pie slices. Looks good

However, blue gets stuck behind the other slices when selected, because it was first in the array. Doesn't look good

I know SVG will stack elements after their order in the DOM and not with a css property such as z-index, so one idea was to remove the selected point and then appending it again. This rearranges the whole pie however, so it's not an alternative.

Is there any other solution for this I'm not thinking of?

document.addEventListener('DOMContentLoaded', function() {
  const chart = Highcharts.chart('container', getChartOptions())
})

/** 
 * Gets the highchart options for the pie chart.
 * Each data point has event handlers added to them 
 * To set the correct border widths
 */
function getChartOptions() {
  return {
    series: [{
      type: 'pie',
      innerSize: '80%',
      allowPointSelect: true,
      slicedOffset: 0,
      states: {
        // Remove default hover settings
        hover: {
          halo: null,
          brightness: 0,
        },
      },
      data: getMockData()
    }],
  };
}

/** 
 * Generates mock data for highcharts
 * Each data point has event handlers set up in the `events` property
 */
function getMockData() {
  return [{
      color: '#aaf',
      borderColor: '#aaf',
      y: 4,
      events: getEventHandlers(),
    },
    {
      color: '#afa',
      borderColor: '#afa',
      y: 3,
      events: getEventHandlers(),
    },
    {
      color: '#faa',
      borderColor: '#faa',
      y: 8,
      events: getEventHandlers(),
    },
  ]
}

/** 
 * Event handlers for highchart data points. 
 * The border width of the slice is set to 20 for `select` and `mouseOver`
 * The border width of the slice is set to 0 for `unselect` and `mouseOut` (except when the event is selected, in which case `mouseOut` shouldn't do anything)
 */
function getEventHandlers() {
  return {
    select: (obj) => {
      obj.target.update({
        borderWidth: 20
      });
    },
    unselect: (obj) => {
      obj.target.update({
        borderWidth: 0
      });
    },
    mouseOver: (obj) => {
      obj.target.update({
        borderWidth: 20
      });
    },
    mouseOut: (obj) => {
      if (!obj.target['selected']) {
        obj.target.update({
          borderWidth: 0
        });
      }
    },
  };
}
/* A drop shadow is the effect I want to accomplish, but it should be above the other slices. */
.highcharts-pie-series .highcharts-point-hover,
.highcharts-pie-series .highcharts-point-select {
  filter: drop-shadow(0 0 10px black);
}
<script src="https://code.highcharts.com/highcharts.js"></script>
<div id="container" style="width:100%; height:400px;"></div>

CodePudding user response:

To achieve extra border when you hovering you can trigger to the point.events and add SVG attributes.

  plotOptions: {
    pie: {
      type: 'pie',
      innerSize: '80%',
      allowPointSelect: true,
      slicedOffset: 0,
      states: {
        hover: {
          halo: null,
          brightness: 0,
        },
      },
      point: {
        events: {
          mouseOver: (obj) => {
            obj.target.graphic.attr({
                'stroke-width': 50,
                stroke: obj.target.color,
                zIndex: 3,
                filter: 'drop-shadow(0 0 10px black)'
              }).css({
                borderRadius: 20
              })
              .add();
          },
          mouseOut: (obj) => {
            obj.target.graphic.attr({
                'stroke-width': 1,
                stroke: obj.target.color,
                filter: 'transparent'
              }).css({
                borderRadius: 0
              })
              .add();
          },
        }
      }

    }
  },

Live demo:

https://jsfiddle.net/BlackLabel/u4ypbsrk/

API References:

https://api.highcharts.com/class-reference/Highcharts.SVGElement#attr,

https://api.highcharts.com/class-reference/Highcharts.CSSObject,

https://api.highcharts.com/highcharts/plotOptions.pie.point.events

CodePudding user response:

Aparently the target you get from the events have a property called graphic which is a handle to the Highcharts.SVGElement of the slice. With that, we can call the .toFront() function which will change the DOM order without changing the order of the slices in the donut.

document.addEventListener('DOMContentLoaded', function() {
  const chart = Highcharts.chart('container', getChartOptions())
})

/** 
 * Gets the highchart options for the pie chart.
 * Each data point has event handlers added to them 
 * To set the correct border widths
 */
function getChartOptions() {
  return {
    series: [{
      type: 'pie',
      innerSize: '80%',
      allowPointSelect: true,
      slicedOffset: 0,
      states: {
        // Remove default hover settings
        hover: {
          halo: null,
          brightness: 0,
        },
      },
      data: getMockData()
    }],
  };
}

/** 
 * Generates mock data for highcharts
 * Each data point has event handlers set up in the `events` property
 */
function getMockData() {
  return [{
      color: '#aaf',
      borderColor: '#aaf',
      y: 4,
      events: getEventHandlers(),
    },
    {
      color: '#afa',
      borderColor: '#afa',
      y: 3,
      events: getEventHandlers(),
    },
    {
      color: '#faa',
      borderColor: '#faa',
      y: 8,
      events: getEventHandlers(),
    },
  ]
}

/** 
 * Event handlers for highchart data points. 
 * The border width of the slice is set to 20 for `select` and `mouseOver`
 * The border width of the slice is set to 0 for `unselect` and `mouseOut` (except when the event is selected, in which case `mouseOut` shouldn't do anything)
 */
function getEventHandlers() {
  return {
    select: (obj) => {
      obj.target.update({
        borderWidth: 20
      });
      obj.target.graphic.toFront();
    },
    unselect: (obj) => {
      obj.target.update({
        borderWidth: 0
      });
    },
    mouseOver: (obj) => {
      obj.target.update({
        borderWidth: 20
      });
      obj.target.graphic.toFront();
    },
    mouseOut: (obj) => {
      if (!obj.target['selected']) {
        obj.target.update({
          borderWidth: 0
        });
      }
    },
  };
}
/* A drop shadow is the effect I want to accomplish, but it should be above the other slices. */
.highcharts-pie-series .highcharts-point-hover,
.highcharts-pie-series .highcharts-point-select {
  filter: drop-shadow(0 0 10px black);
}
<script src="https://code.highcharts.com/highcharts.js"></script>
<div id="container" style="width:100%; height:400px;"></div>

  •  Tags:  
  • Related