PowerBI 全网首发原生平滑曲线 - 原理及实现
仅以本文致敬本科的数学老师们,终于用上了一招。
大家都知道,Power BI 的折线图并没有平滑的曲线,这在很多时候非常不方便。
本文来探讨 Power BI 中原生平滑曲线的实现。这要借助一些成熟的数学算法,我们并不打算研究这些数学算法的具体细节,而是仅仅给出 DAX 实现以及对比。
效果
假设先有一个折线如下:
该折线的问题就是看着太生硬,我们希望它可以更加平滑。得到如下效果:
对于生硬的红色折线,我们希望它可以变得平滑,如蓝线所示。
那么问题来了:
- 如何从红色折线得到蓝色光滑曲线
- 如何确保蓝色线是连续光滑的
- 如何确保蓝色线的生成方式是通用的
为此,我们需要研究从独立散点到形成光滑曲线的方法。
插值算法
我们研究了数学中的几种插值算法,所谓插值,顾名思义,就是在已知的的点之间,插入一些新的值,在连线后,形成整条曲线。我们希望这条曲线满足:
- 连续性
- 最速接近
- 高性能
我们考察了数学中的几种算法,如下:
其中,紫色的 Cubic.Pro 和粉色的 Hermite 是重合的。
可以看出:粉色线是同时满足三个条件的最佳算法。
算法实现
由于 Cubic.Pro 和 Hermite 算法默认重合,这里仅仅使用 Cubic.Pro 算法。
对于某个维度 X ,其每个点可由度量值计算出相应的值。
所谓插值,就是将维度 X 的每两个点之间插入新的节点,可以插入 1 ~ 1000 个点都可以。
而不难猜测,插入的点越多,越平滑,但计算量也越大。
例如:
插入 3 个点时:
很明显,在弯折处是不够光滑的。
插入 10 个点时:
已经很光滑,但在细节处,我们放大看:
还是不够光滑。
插入 20 个点时:
此时已经非常光滑。
这样,我们就得到了从点图(折线图)到完美的光滑曲线的最佳实践,为:
- 采用 Cubic.Pro 插值算法
- 将原来的两个点中插入 20 个点进行插值计算
- 满足连续性以及光滑
- 性能没有问题
DAX实现
第一步,对已有坐标轴进行扩展,如下:
Axis.Ex =
VAR _n = 20 // 用于区间内分割的点数
RETURN
FILTER(
GENERATEALL(
VALUES( 'Axis.X'[X] ) ,
SELECTCOLUMNS( GENERATESERIES( 0 , _n - 1 ) , "X'" , [X] + [Value] / _n )
[X'] <= MAX( 'Axis.X'[X] ) // 去除超过原区间大小的点
)
第二步,实现 Cubic.Pro 插值算法,如下:
Axis.Y.Smooth.Cubic.Pro =
VAR _y_min = CALCULATE( [Axis.Y.Sample] , TREATAS( { MIN( 'Axis.X'[X] ) } , 'Axis.X'[X] ) )
VAR _y_max = CALCULATE( [Axis.Y.Sample] , TREATAS( { MAX( 'Axis.X'[X] ) } , 'Axis.X'[X] ) )
VAR _y0 = COALESCE( CALCULATE( [Axis.Y.Sample] , TREATAS( { VALUES( 'Axis.Ex'[X] ) - 1 } , 'Axis.X'[X] ) ) , _y_min )
VAR _y1 = CALCULATE( [Axis.Y.Sample] , TREATAS( { VALUES( 'Axis.Ex'[X] ) + 0 } , 'Axis.X'[X] ) )
VAR _y2 = CALCULATE( [Axis.Y.Sample] , TREATAS( { VALUES( 'Axis.Ex'[X] ) + 1 } , 'Axis.X'[X] ) )
VAR _y3 = COALESCE( CALCULATE( [Axis.Y.Sample] , TREATAS( { VALUES( 'Axis.Ex'[X] ) + 2 } , 'Axis.X'[X] ) ) , _y_max )
VAR _dx = SELECTEDVALUE( 'Axis.Ex'[X'] ) - SELECTEDVALUE( 'Axis.Ex'[X] )
RETURN
VAR _u1 = _dx
VAR _u2 = _dx * _dx