运用logaX "优雅" 展现可视化图表极端数据🎨

1,218 阅读5分钟

前言

可视化开发是前端开发的重要分支之一。日常的数据需求需要我们去熟练使用EchartsAntvHighcharts等开源可视化库。可能还有些定制化的需求,这就需要去深入了解svgcanvas更底层的东西。操作svg的有D3canvas则有Zrender等。

src=http___img.mp.itc.cn_q_70,c_zoom,w_640_upload_20170629_925694caa5bb411ca71b5702c4739648.jpg&refer=http___img.mp.itc.jfif

本文以Echarts为例,讲解如何运用基本初等函数中的对数函数去解决极端数据的业务场景。如果听懂了,其他图表库相信也能触类旁通地解决。

对数函数🎈

对数函数是6种基本初等函数之一。如果a^x=N(a>0,且a≠1),那么数x叫做以a为底N的对数,记作x=logaN,其中a叫做对数的底数,N叫做真数。

一般,函数y=logax(a>0,且a≠1)叫做对数函数。

下载.png

由图可看出,随着x增长,对数函数的增长趋势越来越平缓,本文的解决核心思想也就是利用对数函数增长缓慢的特性。

进入正题 ~

业务场景

模拟下业务场景。
产品经理:js 需要用折线图表展示这份数据。
后端:返回的真实数据类似[212, 0, 0.001, 9, 1, 133, 13200]

可以看出目前数据差异较大,拿Echarts配置如下所示。

echarts (2).png

let option = {
    xAxis: {
      type: 'category',
      name: 'x',
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    },
    toolbox: {
      show: true,
      feature: {
        saveAsImage: {
            type: 'png'
        }
      }
    },
    tooltip: {
      show: true,
      trigger: 'axis'
    },
    yAxis: {
      type: 'value',
      name: 'y',
      minorSplitLine: {
        show: true
      }
    },
    series: [{
      data: [212, 0, 0.001, 9, 1, 133, 13200],
      type: 'line',
      smooth: true
    }]
};

产品经理:数据差异太大,这图感觉不太行,能不能优化下?
前端:这... 真实数据是这样,能有什么办法?(推锅)给我点时间,我先想想吧。

翻阅Echarts文档,发现轴的type属性有log的概念,立马替换,结果如下,跟想像中的不太一样,好像报错了。😅 echarts (2).png

仔细排查 + 搜索引擎,发现是因为0的原因,毕竟对数x的定义域是大于0的。所以这里可以改成null。图是不是就好看很多了,兴奋地叫产品经理过来看效果。

echarts (2).png

let option = {
    xAxis: {
        type: 'category',
        name: 'x',
        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    },
    toolbox: {
      show: true,
      feature: {
        saveAsImage: {
            type: 'png'
        }
      }
    },
    tooltip: {
      show: true,
      trigger: 'axis'
    },
    yAxis: {
        type: 'log',
        name: 'y',
        minorSplitLine: {
            show: true
        }
    },
    series: [{
        data: [212, null, 0.001, 9, 1, 133, 13200],
        type: 'line',
        smooth: true
    }]
};

产品经理:嗯嗯,好看多了,但怎么中间断开了?
前端:因为数据是0,没有了啊。
产品经理:需求是满足了,好想线是连一起的(得寸进尺💢)
前端:...

虽然嘴上拒绝,但还是得快速地去想方案,毕竟得优雅处理。Echarts没有提供相关解决方案,重新换个库,再研究,有点浪费时间了。自己用canvas画,更不可能了。那怎么去延伸对数的思想呢?

在翻阅对数概念时,突然看到一句话:指数函数是对数函数的反函数,灵光乍现✨。

  1. 先将原始数据用对数函数转换一下。
  2. 显示的时候再用指数函数转换回来。

这里处理原数据的时候需要+1处理,显示的时候-1即可。因为对数在(0,1)范围内是负数,趋于0的时候又接近于负无穷。图表数据本身是正数的情况下,映射成负数,更容易误导。

改造成功,如下图所示,线也连一起了,骄傲地把产品经理叫过来。
产品经理:👏。
前端:小事小事~。

echarts (2).png 代码思路如下:

  1. 转换数据用Math.log10(d + 1)即可。
  2. 显示数据的时候用Math.pow(10, d) - 1。图表方面利用Echarts提供的formatter格式化显示y轴tooltip
    具体代码如下,即插即用,拿去直接用!!!
// 转换原始数据
let transformToLog = (d) => {
    return Math.log10(d + 1)
}
// 对数转原始数据
let transformToOrigin = (d) => {
    return Math.pow(10, d) - 1
}
let orginData = [212, null, 0.001, 9, 1, 133, 13200]

let option = {
    xAxis: {
      type: 'category',
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    },
    toolbox: {
      show: true,
      feature: {
        saveAsImage: {
            type: 'png'
        }
      }
    },
    tooltip: {
      show: true,
      trigger: 'axis',
      formatter: function(params) {
          return params[0].axisValue + ':' + Number(transformToOrigin(params[0].value).toFixed(3))
      }
    },
    yAxis: {
      type: 'value',
      axisLabel: {
        formatter: function(value, index) {
            if(value > 0) { return Math.pow(10, value)}
            return value
        }  
      },
      minorSplitLine: {
        show: true
      }
    },
    series: [{
      data: logData,
      type: 'line',
      smooth: true
    }]
};

产品需求满足了,但故事还没结束,毕竟标题还有极端两个字。

这算是自己的疑问吧,如果数据有负数怎么办,那这又不可行了?毕竟对数定义域是大于0
那怎么办?既然是负数,我们是不是可以想到取绝对值

  1. 若是负数,先取绝对值,再用对数,最后乘以-1
  2. 显示的时候,再反向操作即可。

首先原数据改为:[212, null, 0.001, 9, 1, -133, 13200]。先看结果图,优雅么?

echarts (2).png

// 转换原始数据
let transformToLog = (d) => {
    let operator = 1
    d < 0 && (operator = -1)
    return Math.log10(Math.abs(d) + 1) * operator
}
// 对数转原始数据
let transformToOrigin = (d) => {
    let operator = 1
    d < 0 && (operator = -1)
    return (Math.pow(10, d * operator)) * operator - 1 * operator
}
let orginData = [212, null, 0.001, 9, 1, -133, 13200]
let logData = orginData.map(item => transformToLog(item))

let option = {
    xAxis: {
      type: 'category',
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    },
    toolbox: {
      show: true,
      feature: {
        saveAsImage: {
            type: 'png'
        }
      }
    },
    tooltip: {
      show: true,
      trigger: 'axis',
      formatter: function(params) {
          return params[0].axisValue + ':' + Number(transformToOrigin(params[0].value).toFixed(3))
      }
    },
    yAxis: {
      type: 'value',
      axisLabel: {
        formatter: function(value, index) {
            let operator = 1
            if(value < 0) operator = -1
            if(value === 0) return 0
            return Math.pow(10, value * operator) * operator 
            
        }  
      },
      minorSplitLine: {
        show: true
      }
    },
    series: [{
      data: logData,
      type: 'line',
      smooth: true
    }]
};

总结

💗 感谢8月掘金活动,让自己能坚持一个月,用文字记录基础知识点和一些小心得,也幸运地获取了一些点赞和关注,从lv1lv2,切实感受写文章的快乐。

先前文章大多是JS基础,接下来,我会分享的更全面一点,比如CSSnode可视化浏览器等,也会继续分享自己的小心得(结合真实业务场景,绝对干货),比如这一篇,看大家兴趣(其实看点赞量了,哈哈哈哈)。

如果本篇文章帮到你了,记得毫不吝啬地三连点赞+关注+收藏啊!!!

当然,也欢迎各路大佬评论交流学习,批评指正。