使用 DAX 在 Power BI 中创建自定义可视化元素

本文翻译自Kurt Buhler的文章—《Creating custom visuals in Power BI with DAX》来源:SQLBI本文介绍如何在使用DAX度量值中创建动态图像,以便在表格或卡片中可视化数据。内容包括在Figma中轻松实现这一功能的方法,以及在实际应用中使用这些DAX度量值时的注意事项。

在Power BI中创建报告时,您经常会遇到需要以非标准“核心可视化”工具直接提供的方式展示数据的场景。有时,可视化效果需要反映组织的业务流程或数据的特殊性。其他时候,您可能只是想采用更具创意的设计,以最有效的方式展示数据。本文探讨了在这些场景下应该如何操作,介绍了一种使用单个DAX度量值创建自定义可视化的方法,并指出了这种特定方法的注意事项和局限性。

在这篇文章中,我们的目标是使用Timeline SVG制作一个矩阵可视化图表,就像下面的图表一样。

这个报告中的矩阵看起来不错,但我们为此付出了代价。正如您将在本文后面看到的,添加这样的自定义可视化元素会增加复杂性,使报告更难维护。与更简单的方法相比,这种复杂性的成本可能会超过其潜在收益,但我们可以通过使用模板和记录自定义可视化元素来减轻这种复杂性。

在本文的其余部分,我们将解释如何制作这样的自定义可视化元素以及制作时的注意事项。

场景概述

考虑以下跟踪准时交货(OTD)性能的场景。为了简化这个真实场景,我们使用了SpaceParts Co.示例数据集。这是一个用于学习Power BI的复杂示例数据集,可从Tabular Editor免费获取。

在这种情境下,一个组织旨在通过提高按时交付订单的比例来提升客户满意度。当订单在要求的货物收货日期之前或当天送达时,即视为按时交付。客户服务团队希望获得一份详细报告,以调查已经延误或存在延误风险的重要订单。

他们要求的报告包含大量信息。例如,在这份报告中,他们最初想要一个包含许多字段的、详细到订单明细项的大表格。这些字段将包括几个不同的日期,您可以在下面表格中看到一个随机选择的订单示例。

这张表格已经让人应接不暇,而且它甚至没有包含所请求列的一半。仅有几行数据,但阅读和理解各个日期字段及其之间的差异就需要很长时间。在与该团队讨论后,他们认可在表格中加入可视化时间线会有所帮助。这次讨论的结果是,他们绘制了一幅草图(或线框图),展示了他们希望在这份报告的表格中看到的样子。

在线框图中,三个日期字段被替换为相对时间线。每个日期都由时间线上的一个点表示,这些点相互连接。当实际的货物收货确认日期晚于目标货物收货请求日期时,红色形状会表示订单延误了多久。相反,如果订单将按时交付,则货物收货确认日期的连接点为蓝色。

虽然可视化图表可以显示以天为单位的绝对时间线,但在这个特定情况下,绝对日期对用户来说不太重要。相反,业务方希望看到任何给定订单明细项相对于订单日期和请求货物收货日期之间的时间来说,是提前还是延迟。

虽然所请求的可视化图表可能不是最理想的,但相对于原始的表格而言,它对客户服务团队来说似乎更有用(而且肯定更优雅)。然而,不幸的是,在Power BI中,我们没有现成的可视化图表、格式或其他选项可以用来创建它。那么……我们能做些什么呢?

当“Power BI 无法实现”时,应该怎么做?

当您在 Power BI 的核心可视化图表中无法满足报告要求时,请不要轻易放弃!除了寻找替代方法外,您还可以考虑六种可能的自定义可视化图表选项之一。

“麦金塔式”改造核心可视化图表:此方法得名于80年代的动作英雄麦金塔(MacGyver),他因擅长运用非传统方法改造日常工具来解决问题而闻名。就像麦金塔一样,在这种方法中,我们以非传统的方式使用Power BI可视化图表的格式选项来达到我们想要的效果。虽然它本身不是自定义可视化图表,但我们确实以非典型的方式对核心可视化图表进行了定制。例如,在条形图可视化图表中使用误差线来创建子弹图。虽然这是最简单、最容易的选择,但如果你过度使用,“麦金塔式”改造的可视化图表仍然容易出现问题,维护成本也会更高。这些可视化图表通常是一个不错的选择,但在它们所能创建的格式选项和图表类型方面存在限制。


AppSource中的自定义可视化图表:你可以在Power BI Desktop中下载并使用预先制作好的自定义可视化图表。这些自定义可视化图表由其他人或第三方供应商制作。它们可能是开源的,但有些可视化图表可能需要付费许可证才能使用。值得注意的是,商业自定义可视化图表提供来自供应商的直接支持;相比之下,免费可视化图表则“按原样”提供,不保证提供支持。此外,对于所有自定义可视化图表,你通常只能在其开发者提供的选项范围内进行定制。AppSource中的自定义可视化图表必须由你所在组织中Power BI/Fabric租户的管理员启用。如果它们能满足你的需求,并且在格式、交互或性能方面没有其他限制,那么这些可视化图表是一个不错的选择。


DAX度量中的可缩放矢量图形(SVG)自定义可视化图表:你可以在DAX中指定SVG图像的规格,从而动态渲染矢量形状来创建自定义可视化图表。SVG可视化图表在Power BI中受原生支持,无需额外下载或插件。如果你需要将它们用作“微型可视化图表”(即较大可视化图表(如卡片或表格)的补充部分),那么这些可视化图表通常是一个不错的选择。在本文后面,我们将更详细地解释SVG可视化图表,包括它们的注意事项。


Power BI中的Deneb声明式可视化图表:你可以使用Deneb通过Vega或Vega-Lite规范(一种类似JSON的语法,用于定义可视化图表的工作方式和外观)来自定义可视化图表。这些规范对于人类和大型语言模型(如ChatGPT)来说都易于阅读和理解。Deneb实际上是AppSource中的一个开源自定义可视化图表,但它在提供创建自定义可视化图表的框架,甚至保存和重用模板方面独树一帜。Deneb非常成熟,拥有一个活跃的社区,提供了许多此类模板;然而,编写、测试和管理可视化图表规范需要更多时间。我们将在未来的文章中更详细地解释如何使用Deneb。


Python或R自定义可视化图表:Power BI报告原生支持Python或R集成。这意味着如果你本地安装了Python或R,你可以使用像Python中的seaborn库或R中的ggplot2包及其扩展所制作的可视化图表。如果你了解这些语言,或者需要更多的统计或科学功能,那么Python或R可视化图表可能是一个不错的选择。然而,对于普通的Power BI开发人员来说,这段代码在复杂性和维护方面都是一个巨大的挑战。


Javascript自定义可视化图表(Power BI可视化API):你可以像AppSource中提供的那样,使用Javascript创建自己的Power BI自定义可视化图表。以这种方式创建自定义可视化图表通常被认为是可用选项中最复杂的一种。如果你了解Javascript,这可能是一个不错的选择,但要求你理解和有效使用Power BI的可视化API。

像所有事物一样,选择有很多,而使用哪一个取决于你的具体情况。然而,在从这些不同的选项中进行选择之前,你应该首先决定你是否应该致力于制作自定义的可视化图表,还是寻找一种替代的、更简单的方法。

选择是否制作自定义可视化图表

之前的选项在很多方面都有所不同,但通常,复杂性的增加意味着灵活性和定制性的提升,而这需要付出代价。在决定制作自定义可视化图表之前,你必须始终考虑这种复杂性所带来的成本:

制作成本:创建自定义可视化图表可能需要更多时间,因为在使用它之前,你必须对其进行定义、测试和优化。当你需要编写自定义代码时,这一点尤其正确。你可以使用经过验证的模板(如脚本或Deneb模板)来抵消这一成本。


维护成本:自定义可视化图表的维护可能需要更多时间。此外,你的团队成员可能不知道你是如何创建该可视化的,因此如果需要修复或更改它,他们可能会遇到困难。你可以通过记录自定义可视化并培训同事如何维护它来抵消这一成本。


(可选)使用成本:如果你需要为从AppSource使用自定义可视化图表而支付许可费用,那么这将产生额外的成本。
由于这些额外的成本,通常,你应该首先考虑更简单、核心的可视化图表,然后再考虑自定义可视化图表。当存在明确的业务案例或对报告使用者有明显好处时,才应使用自定义可视化图表。有很多情况下,人们为了追求令人印象深刻或美观的设计而创建了过于复杂的报告,但最终结果是报告难以维护或使用起来非常缓慢。

如果你选择不制作核心可视化图表,那么你必须向用户提出替代方案,并尝试找到折衷办法。

你可能选择不制作自定义可视化图表的一些原因包括:

你已经可以使用Power BI的核心可视化图表来制作该可视化。
你或你的组织缺乏构建或维护自定义可视化的成熟度或技能。
没有足够的时间来投入制作自定义可视化图表。
与Power BI的核心可视化图表相比,自定义可视化的性能太慢。
好处仅限于美观(如在条形图中将条形顶部变圆或使用自定义字体)。通常,你应该避免仅因美观原因而选择图表类型。
在我们的场景中,我们选择制作自定义可视化图表,因为有限的制作成本被客户服务团队的实用性和便利性所抵消。在真实场景中,我们还会考虑支持该可视化的成本,包括未来更改的可能性(如添加额外的日期字段、标签等)。

选择哪种方法

在选择Power BI中制作自定义可视化的六种方法之一时,你应该考虑以下几个因素:

该方法是否支持你的用例;是否可能实现你的设计。
构建和测试可视化所需的时间投入。
你和你的团队在Power BI方面的成熟度以及使用特定工具或语言的技能。
在我们的场景中,我们选择使用SVG可视化图表,原因如下:

我们可以在Power BI的原生表格可视化图表中使用它来获得我们想要的结果。
在Figma和Tabular Editor等工具的帮助下,我们可以快速制作可视化图表。
在本文的其余部分,我们将解释如何制作这个可视化图表。

在DAX度量值中使用SVG创建自定义可视化图表

在Power BI中,列和度量值都可以包含图像信息,报告可以在某些可视化图表中呈现这些信息。这些图像信息可以来自网络上的图像的超链接、使用base64编码的图像数据或SVG图像规范。

可缩放矢量图形(SVG)是一种使用代码(特别是XML)定义矢量的图像格式。SVG图像由不同的分层矢量组成,这与PNG等位图格式不同,因为SVG图像可以放大或缩小而不会损失分辨率。由于SVG只是一个规范,这意味着图像实际上是代码。因此,我们可以将SVG定义为列或DAX度量值的字符串值。为此,SVG规范字符串需要一个特殊的前缀“data:image/svg+xml;utf8, ”,以便Power BI可视化图表能够理解该度量值可以呈现为图像。

以下是在DAX度量值中该字符串的示例。请注意,SVG规范已被更改,因此所有双引号(“)都已更改为单引号(‘)。或者,你可以通过在规范中包含两个相邻的引号(“”)来转义双引号,这是我们在本文后面所做的。该规范生成了一个SVG图像,用于指示订单行的状态,我们在本文前面已经展示过。

SVG度量表中的度量

Status SVG =
"data:image/svg+xml;utf8,

LATE"

如果此度量的数据类别属性设置为ImageUrl(在Power BI Desktop中为Image URL),那么一些可视化对象会将代码渲染为图像。在表格和矩阵中,您还可以通过在“格式”窗格中调整可视化对象的“图像大小”属性来改变图像的大小;不过,其他可视化对象也支持渲染图像,例如新的卡片可视化对象和新的切片器可视化对象,以及散点图背景。

在下面的图表中,您可以看到此状态SVG的示例,以及哪些可视化对象支持从DAX度量中渲染图像的说明。

![](https://pic3.zhimg.com/v2-3ac6204d1ea1a70f1fab0fdee48af352_1440w.jpg)

回顾一下,在Power BI中,一个包含SVG规范字符串的DAX度量在以下条件下可以将其渲染为图像:

SVG规范字符串中包含适当的前缀(data:image/svg+xml;utf8,)。 SVG规范是有效的,并且在度量中也是一个有效的字符串(即双引号被转义)。 数据类别属性设置为Image URL(图像URL)。 度量被放置在支持渲染图像的可视化对象的字段井中(如表格)。 你可能会看到一个SVG规范并认为它很复杂——而你的这种感觉是对的。对于普通的Power BI开发者来说,选择这种方法会大大增加复杂性。然而,你不需要手动编写这段代码。如果你完全不会制作SVG,那么你可以仅使用像Figma这样的设计工具,或者将其与像ChatGPT这样的LLM(大型语言模型)结合使用,从而取得很大的进展。

使用Figma简化SVG创建过程

要创建带有SVG的自定义可视化对象,你必须定义构成图表的矢量。通常,这意味着你要么从头开始手动编写规范,要么从现有的模板开始。你可以使用许多优质的社区SVG模板,包括Kerry Kolosko、Andrzej Leszkiewicz、Štěpán Rešl、David Bacci等人提供的模板。

要创建自己的SVG,你可以使用任何设计工具,比如Figma。Figma具有许多有用的功能,包括SVG规范与其渲染的矢量之间的跨兼容性。基本上,你可以在Figma中绘制形状,然后直接从Figma用户界面中的图像复制SVG规范。反之亦然,你可以复制一个SVG规范并将其粘贴到Figma中作为图像。

在Figma应用程序中,你可以通过右键单击图像来复制SVG规范,如下面的Figma设计文件图示所示。

![](https://picx.zhimg.com/v2-d6f180d8674585fe0e9f87618eb33ef3_1440w.jpg)

要在Power BI Desktop的表格可视化对象中渲染此图像,我们只需将SVG规范设置为DAX度量中的有效字符串,并将数据类别属性设置为“图像URL”。

![](https://pic2.zhimg.com/v2-fa93ce638aa360f775aa23e707744803_1440w.jpg)

从前面的图片中您可以看出,SVG规范很复杂,并且代码无法手动维护。但是,我们可以在Figma的用户界面中完全管理它。这意味着您无需了解如何生成此代码的详细信息,而只需知道如何在Figma中设计您所需的内容即可。

这种方法的一个优点是,如果我们使用Figma来模拟任何自定义SVG可视化对象,那么我们将立即获得SVG规范。但是,请注意,Figma通常会为某些矢量(如文本)使用更复杂的标签,这在DAX中可能难以理解或使其动态化。

在Figma中设计我们的自定义SVG可视化对象

回到我们的“准时交货”场景,我们现在有一种方法可以将草图轻松转换为SVG。为此,我们将设计拆分为基本组件,并使用Figma中的形状绘制每个组件。在Figma中绘制形状与在PowerPoint和其他工具中绘制形状相同,无需特定培训或熟练度。

在几分钟内,我们就可以获得可视化对象的Figma模拟图,并复制SVG规范以供在Power BI中使用。

![](https://picx.zhimg.com/v2-adac776a35ad90953a43bdb6029a94b1_1440w.jpg)

包含SVG规范的最终度量表达式如下:

SVG度量表中的度量

Timeline SVG = ""

目前,这个度量值无法在表格或矩阵可视化对象中呈现。我们首先需要调整SVG规范,使其成为一个有效的字符串,这意味着我们需要对双引号进行转义,添加前缀,并删除一些不必要的元素,如viewBox和fill,这些元素在渲染图像时是不需要的。

我们还可以选择让SVG规范更易读。这将有助于我们在下一步添加动态值时。我们将每个矢量拆分到它们自己的变量中,并为重要值(如位置、颜色和高度)设置变量。在将这些变量添加回规范中时,我们需要用”& _TheVariable &“替换字符串的一部分(子字符串)。这样做通常会使可视化对象更易于更改和维护。

包含字符串的最终度量值如下所示:

SVG度量表中的度量

Timeline SVG (With Variables; Not Dynamic) = VAR _SvgWidth = 75 VAR _SvgHeight = 7 VAR _AxisMax = _SvgWidth / 2

VAR _OnTargetFill = "#448FD6" -- Blue VAR _OnTargetStroke = "#2F6698" -- Dark Blue

VAR _OffTargetFill = "#D64444" -- Red VAR _OffTargetStroke = "#982F2F"-- Dark Red

VAR _SvgPrefix = "data:image/svg+xml;utf8, " -- Required to parse the result as an image VAR _SvgSuffix = ""

VAR _Background = "" VAR _Axis = "" VAR _Origin = "" VAR _DumbbellLine = "" VAR _TargetIndicator = "" VAR _ActualCircle = ""

VAR _Svg = _SvgPrefix & _Background & _Axis & _Origin & _DumbbellLine & _TargetIndicator & _ActualCircle & _SvgSuffix

RETURN _Svg

如您所见,即使将SVG规范拆分,它也会变得相当复杂且难以阅读。我们将在本文的后面部分深入探讨这种复杂性为何会带来问题。

目前,图像现在可以在矩阵中呈现,但会在每一行中重复。

![](https://picx.zhimg.com/v2-688eac3e41b1c38d51ead0e93cdf9f1d_1440w.jpg)

要将静态SVG图像转换为数据驱动的可视化对象,我们需要使用DAX将字符串的部分替换为动态值。

使用DAX使SVG动态化

SVG规范定义了各种属性,这些属性进而定义了每个矢量的位置、长度和大小。目前,这些属性在SVG字符串中是静态且硬编码的。为了创建我们的可视化对象,我们将这些静态值替换为动态评估的值。

在这种情况下,我们的可视化对象旨在显示几个不同的数据点。我们创建了几个度量值来计算这些数据。首先,是我们的“交货天数”:

订单表中的度量值

Days to Delivery = AVERAGEX( 'Orders', DATEDIFF ( 'Orders'[Confirmed Goods Receipt Date], 'Orders'[Order Date], DAY ) )

接下来是“请求交货天数”,这是我们的目标:

订单表中的度量值

Days to Requested Delivery = AVERAGEX( 'Orders', DATEDIFF ( 'Orders'[Requested Goods Delivery Date], 'Orders'[Order Date], DAY ) )

接下来,我们想将这些度量值添加到SVG中,使用它们来定义每个圆的位置、线条的长度以及条件格式:

订单表中的度量值

Timeline SVG (Intermediate) = -- Measures used in the custom SVG visual VAR _Actual = [Days to Deliver] VAR _Target = [Days to Requested Delivery]

-- SVG size and absolute position configuration VAR _SvgWidth = 75 VAR _SvgHeight = 20 VAR _AxisMax = _SvgWidth / 2 -- Position of the target along the X-axis

-- Conditional formatting configuration VAR _OffTargetFill = "#D64444" -- Red VAR _OnTargetFill = "#448FD6" -- Blue VAR _Fill = IF ( _Actual > _Target, _OffTargetFill, _OnTargetFill )

VAR _OffTargetStroke = "#982F2F" -- Dark Red VAR _OnTargetStroke = "#2F6698" -- Dark Blue VAR _Stroke = IF ( _Actual > _Target, _OffTargetStroke, _OnTargetStroke )

-- Determining the position of the actuals relative to the target VAR _ActualPosition = (DIVIDE ( _Actual, _Target ) * _AxisMax) + 5

-- SVG prefix and suffixes VAR _SvgPrefix = "data:image/svg+xml;utf8, " VAR _SvgSuffix = ""

-- Vectors VAR _Timeline = ""

VAR _Origin = -- Order Date ""

VAR _Variance = ""

VAR _TargetIndicator = ""

VAR _ActualCircle = ""

VAR _Svg = _SvgPrefix & _Timeline & _Origin & _Variance & _TargetIndicator & _ActualCircle & _SvgSuffix

RETURN _Svg

现在,生成的可视化对象将动态显示每个订单行相对于其自身的“请求货物收据日期”是提前还是延迟。

![](https://picx.zhimg.com/v2-71d89cf7c1fd82ee0cf4aa4bd792cb3d_1440w.jpg)

可视化效果已经相当不错。然而,也存在一些特殊情况,即线条超出了图像的边界。在设计可视化对象时,这是很常见的情况:在实现时,可视化对象所用的数据往往包含一些你在设计时无法预料到的怪异和异常情况。在这里,我们需要处理两种情况:

尚未发货或交付的订单:对于这些订单,我们添加一个条件:当“发货日期”或“确认货物收据日期”为空(在“订单状态”度量值中确定)时,我们显示“进行中天数”而不是“交货天数”(因为订单尚未交付)。

异常延迟的订单:对于这些订单,我们限制了可能的最大值,并在最右侧位置添加一个箭头,以视觉上表明实际值超出了范围。

这为我们自定义的SVG可视化对象提供了最终代码(由于代码较长,此处为简洁起见省略,但你可以在示例文件中找到最终代码)。现在,生成的可视化对象符合预期效果。

![](https://pic1.zhimg.com/v2-2bdb599680f13e7233e0aab8eaa36a26_1440w.jpg)

从这里开始,你可以尝试自己对可视化对象进行一些额外的更改或改进:

将可视化对象从相对时间(按时或延迟)更改为绝对天数。 添加标签(日期或它们之间的差异)。 添加其他数据点或线条(如发货日期)。 在示例文件中,你可以找到一个用于Tabular Editor的C#脚本,以将此可视化对象添加到你自己的报告中,因为它会提示你选择你打算比较的实际值和目标值。

然而,在你走得太远之前,是时候踩下刹车了。我们需要考虑这种方法的成本,以及它对我们未来使用和维护它的意义。

关于度量值中SVG自定义可视化的注意事项和担忧

如你所见,为了实现这一结果,定义SVG的DAX度量值变得冗长、复杂且难以阅读或更改。如果没有对这段代码进行注释和记录,它将很难维护。此外,随着复杂性的增加,出现错误和性能问题的可能性也会增加。

除了增加的复杂性之外,这种方法的另一个令人担忧的地方是“SVG度量值”应该存放在哪里。像这样的DAX度量值不属于语义模型。它只支持一个可视化需求,并且不能在此上下文之外使用。我们在之前的文章中已经讨论过这一担忧,并权衡了将此类报告特定对象包含在精简报告(如报告度量值)或复合模型中的考虑因素。然而,这两种替代方案都有其局限性。

至少,SVG度量值应该与模型的其他部分隔离开来,放在它们自己的度量值表中并隐藏起来。如果你使用Tabular Editor管理模型,你还可以为此度量值表启用“私有”属性,以确保度量值无法在客户端工具或DAX自动完成中查看。当人们连接到模型以制作自己的报告时,这是一个不错的选择。

这些增加的成本必须与在你的报告中使用这种方法的潜在收益仔细权衡。SVG度量值可能很有价值并且有其用例……随着时间的推移,它们在报告中似乎越来越普遍和受欢迎。但是,要踩下刹车……首先考虑一下你是否可以使用更简单、更可持续的方法达到类似的结果。

结论

在创建可视化对象时,你经常会遇到需要额外灵活性和自定义的场景。在这些情况下,你可能会选择六种不同的方法之一来制作自定义可视化对象,就像在DAX度量值中的动态SVG一样。使用像Figma这样的工具来设计这些SVG很简单,但规范和DAX可能会迅速变得复杂,并且可能会污染语义模型。重要的是要知道,当你的需要时,这些选项在你的工具包中是存在的。

然而,你必须牢记,Power BI报告的最终目的是要实用,而不仅仅是看起来漂亮。如果你能够使用更直接的方法创建有用的报告,那么你应该这样做。

​ >------ 如果您想深入学习微软Power BI,欢迎登录网易云课堂试听学习我们的“从Excel到Power BI数据分析可视化”系列课程。或者关注我们的公众号(PowerPivot工坊)后猛戳”在线学习”。
![](https://pbihub.cn/uploads/images/201809/26/125/B4knFMBlIp.jpg) ​ ----- ​ 长按下方二维码关注“Power Pivot工坊”获取更多微软Power BI、PowerPivot相关文章、资讯,欢迎小伙伴儿们转发分享~
![](https://p1-tt.byteimg.com/origin/pgc-image/d266388e16184f8aaf3a2c426504c2e3?from=pc)

Power Pivot工坊