Highcharts - 使用选定的饼图切片获得 3d 效果

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

在 highcharts 中,我试图做到这样,当用户选择或将鼠标悬停在饼图的一个切片上时,该切片会产生在 z 轴(朝向用户)上上升的效果。我试图通过 css 设置阴影过滤器并使切片的边界更宽(填充颜色相同)来实现这一点。然而,我面临的问题是这些切片仍然可以在其他切片下方,因此所选切片及其阴影将位于这些切片的后面,因此看起来仍然在它们下面。进一步说明:

由于最后添加了红色,因此选中时看起来不错 - 它位于其他饼图上方。

但是,蓝色在被选中时会卡在其他切片后面,因为它在数组中排在第一位。

我知道 SVG 会按照 DOM 中的顺序堆叠元素,而不是 css 属性,例如 z-index,所以一个想法是删除选择点,然后再次附加它。然而,这会重新排列整个馅饼,因此它不是替代方案。

有没有其他我没有想到的解决方案?

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>

要在悬停时获得额外的边框,您可以触发 point.events 并添加 SVG 属性。

  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();
          },
        }
      }

    }
  },

现场演示:

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

API 参考文献:

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

显然,您从事件中获得的目标有一个名为 graphic 的 属性,它是切片 Highcharts.SVGElement 的句柄。这样,我们就可以调用 .toFront() 函数来改变 DOM 的顺序,而不改变甜甜圈中切片的顺序。

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>