Vue使用G2Plot-Pie饼图实现数据渲染-和类似0.1+0.2导致的精度问题

Posted JackieDYH

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue使用G2Plot-Pie饼图实现数据渲染-和类似0.1+0.2导致的精度问题相关的知识,希望对你有一定的参考价值。

前言

笔者,在使用Pie饼图的时候,有时候需要调整显示内容,比如在饼图中间显示总计金额,

但是,在使用的时候,会发现一个经典的问题,javascript计算类似0.1+0.2的时候,会出现精度失真的情况,那么这个时候就需要自己二次处理一下计算过程,已解决失真的问题

 图示

 首先定义组件

<!--
 * @Author: Jackie
 * @Date: 2022-06-16 15:09:39
 * @LastEditTime: 2022-07-04 16:48:11
 * @LastEditors: Jackie
 * @Description: 饼图
 * @FilePath: /white-label-nft-admin-client/src/components/Charts/PieChart.vue
 * @version: 
-->
<template>
  <div id="PieChart" ref="PieChart"></div>
</template>

<script>
import  Pie  from '@antv/g2plot'
let chartChange
export default 
  name: 'PieChart',
  props: 
    value: 
      type: Array,
      default() 
        return []
      ,
    ,
    Height: 
      type: Number,
      default: 0,
    ,
  ,
  // 监听
  watch: 
    value: 
      handler(newVal, oldVal) 
        console.log(newVal)
        // 更新数据
        chartChange.changeData(newVal)

        // 销毁后再渲染
        // chartChange.destroy()
        // this.initG2Plot()
      ,
      deep: true, //深度监听
      // immediate: true,
    ,
  ,
  mounted() 
    this.initG2Plot()
  ,
  methods: 
    initG2Plot() 
      chartChange = new Pie(this.$refs.PieChart, 
        data: this.value,
        height: this.Height,
        appendPadding: 10,
        angleField: 'value',
        colorField: 'type',
        radius: 0.9,
        innerRadius: 0.64,
        meta: 
          value: 
            formatter: (v) => `¥ $v`,
          ,
        ,
        legend: 
          ayout: 'vertical',
          position: 'right',
          flipPage: true,
          offsetX: -20,
          // title: 
          //   text: '产品类别 (销售量占比)',
          //   spacing: 8,
          // ,
          // itemValue: 
          //   formatter: (text, item) => 
          //     const items = this.value.filter((d) => d.type === item.value)
          //     return items.length ? '¥' + items.reduce((a, b) => a + b.value, 0) / items.length : '-'
          //   ,
          //   style: 
          //     opacity: 0.65,
          //   ,
          // ,
        ,
        label: 
          type: 'outer',
          content: 'name - value元 | 占比percentage',
          // type: 'inner',
          // offset: '-50%',
          // autoRotate: false,
          // style:  textAlign: 'center' ,
          // formatter: ( percent ) => `$(percent * 100).toFixed(0)%`,
          style: 
            fill: '#000',
          ,
        ,
        // 圆心配置
        statistic: 
          title: 
            offsetY: -10,
            content: '总计',
          ,
          content: 
            offsetY: 10,
          ,
        ,
        // 添加 中心统计文本 交互
        interactions: [
           type: 'pie-legend-active' ,
           type: 'element-active' ,
          //  type: 'element-selected' ,
          // 
          //   type: 'pie-statistic-active',
          //   cfg: 
          //     start: [
          //        trigger: 'element:mouseenter', action: 'pie-statistic:change' ,
          //        trigger: 'legend-item:mouseenter', action: 'pie-statistic:change' ,
          //     ],
          //     end: [
          //        trigger: 'element:mouseleave', action: 'pie-statistic:reset' ,
          //        trigger: 'legend-item:mouseleave', action: 'pie-statistic:reset' ,
          //     ],
          //   ,
          // ,
        ],
      )
      chartChange.render()
    ,
  ,

</script>

使用的时候当传入数据有小数时,容易出现问题

[
	 type: '活动01', value: 20 ,
	 type: '活动02', value: 30 ,
	 type: '活动03', value: 30 ,
	 type: '活动04', value: 50 ,
	 type: '活动05', value: 20 ,
	 type: '活动06', value: 80.14 ,
	 type: '活动07', value: 0.01 ,
]

解决问题

当出现这个问题时,我就明白是JavaScript计算精度导致的,那么只能自己处理这个累计的值,来解决这个问题

查阅官方文档

参照文档

解决方法:失真是由于小数相加导致的,那么我们在相加之前,先扩大100倍,得到结果后缩小100倍,这样正确的值就得到了

// 圆心配置
statistic: 
  title: 
	offsetY: -10,
	content: '总计',
  ,
  content: 
	offsetY: 10,
	// 解决精度失真问题
	// 方法一:value * 1000000000000
	// 方法二:parseFloat((0.1+0.2).toFixed(5))
	formatter: (value, datum, index, data) => 
	  const total = datum.reduce((a, b) => 
		return a + b.value
		// return a + b.value * 1000000000000
	  , 0)
	  return `¥$parseFloat(total.toFixed(5))`
	  // return `¥$total / 1000000000000`
	,
   ,
,

图示


全量代码

<!--
 * @Author: Jackie
 * @Date: 2022-06-16 15:09:39
 * @LastEditTime: 2022-07-04 17:00:37
 * @LastEditors: Jackie
 * @Description: 饼图
 * @FilePath: src/components/Charts/PieChart.vue
 * @version: 
-->
<template>
  <div id="PieChart" ref="PieChart"></div>
</template>

<script>
import  Pie  from '@antv/g2plot'
let chartChange
export default 
  name: 'PieChart',
  props: 
    value: 
      type: Array,
      default() 
        return []
      ,
    ,
    Height: 
      type: Number,
      default: 0,
    ,
  ,
  // 监听
  watch: 
    value: 
      handler(newVal, oldVal) 
        console.log(newVal)
        // 更新数据
        chartChange.changeData(newVal)

        // 销毁后再渲染
        // chartChange.destroy()
        // this.initG2Plot()
      ,
      deep: true, //深度监听
      // immediate: true,
    ,
  ,
  mounted() 
    this.initG2Plot()
  ,
  methods: 
    initG2Plot() 
      chartChange = new Pie(this.$refs.PieChart, 
        data: this.value,
        height: this.Height,
        appendPadding: 10,
        angleField: 'value',
        colorField: 'type',
        radius: 0.9,
        innerRadius: 0.64,
        meta: 
          value: 
            formatter: (v) => `¥ $v`,
          ,
        ,
        legend: 
          ayout: 'vertical',
          position: 'right',
          flipPage: true,
          offsetX: -20,
          // title: 
          //   text: '产品类别 (销售量占比)',
          //   spacing: 8,
          // ,
          // itemValue: 
          //   formatter: (text, item) => 
          //     const items = this.value.filter((d) => d.type === item.value)
          //     return items.length ? '¥' + items.reduce((a, b) => a + b.value, 0) / items.length : '-'
          //   ,
          //   style: 
          //     opacity: 0.65,
          //   ,
          // ,
        ,
        label: 
          type: 'outer',
          content: 'name - value元 | 占比percentage',
          // type: 'inner',
          // offset: '-50%',
          // autoRotate: false,
          // style:  textAlign: 'center' ,
          // formatter: ( percent ) => `$(percent * 100).toFixed(0)%`,
          style: 
            fill: '#000',
          ,
        ,
        // 圆心配置
        statistic: 
          title: 
            offsetY: -10,
            content: '总计',
          ,
          content: 
            offsetY: 10,
            // 解决精度失真问题
            // 方法一:value * 1000000000000
            // 方法二:parseFloat((0.1+0.2).toFixed(5))
            formatter: (value, datum, index, data) => 
              const total = datum.reduce((a, b) => 
                return a + b.value
                // return a + b.value * 1000000000000
              , 0)
              return `¥$parseFloat(total.toFixed(5))`
              // return `¥$total / 1000000000000`
            ,
          ,
        ,
        // 添加 中心统计文本 交互
        interactions: [
           type: 'pie-legend-active' ,
           type: 'element-active' ,
          //  type: 'element-selected' ,
          // 
          //   type: 'pie-statistic-active',
          //   cfg: 
          //     start: [
          //        trigger: 'element:mouseenter', action: 'pie-statistic:change' ,
          //        trigger: 'legend-item:mouseenter', action: 'pie-statistic:change' ,
          //     ],
          //     end: [
          //        trigger: 'element:mouseleave', action: 'pie-statistic:reset' ,
          //        trigger: 'legend-item:mouseleave', action: 'pie-statistic:reset' ,
          //     ],
          //   ,
          // ,
        ],
      )
      chartChange.render()
    ,
  ,

</script>

以上是关于Vue使用G2Plot-Pie饼图实现数据渲染-和类似0.1+0.2导致的精度问题的主要内容,如果未能解决你的问题,请参考以下文章

vue解决echart饼图比例过小影响交互的问题

31-Vue之ECharts-饼图

vue+ECharts组件封装及饼图实现圆环进度条

隐藏饼图数据为0项区域

Springboot项目中运用vue+ElementUI+echarts前后端交互实现动态圆环图

单独图表组件的开发 --- 库存与销量模块(圆环饼图 )