一、引言
今天的PowerBI报表的制作相对有一点复杂,我们直接根据最终展示图来讲解:
可以看到,我们今天要制作的图像需要包括以下几点:时间维度的趋势、两种不同维度的数据对比、不同数据标签的展示、不同年份间环比的标签展示以及上方趋势线以及趋势标签的制作。
最终清晰展现交互性的时间趋势环比双柱状图。
二、具体教学
那么该如何制作这样一张表呢?
我们的基础数据源需要用到三列数据:
【日期列】(图中年份数据)、【双柱状图数据】(图中浅蓝色为中国部分的数据,橙色为Global部分数据)
首先我们需要用到的可视化对象:
接着我们需要构建在两个基础度量值,分别计算当前计算到的中国部分数据总和以及Global部分数据总和:
Current_year_CNPVO_value = sum('PVO&CNI_BI'[Total CHN PVO])
Current_year_GlobalPVO_value = sum('PVO&CNI_BI'[Global PVO])
接着我们绘制我们的双柱状图的代码:
Bar_PVO =
// 'PVO&CNI_BI'[YEAR] ), [000 Current_year_CNPVO_value], [000 Current_year_GlobalPVO_value]VAR Width_Bar =80 //每个柱形空间宽度
VAR Width_BarAct =35 //每个柱形实际宽度(减小宽度以容纳两个柱子)
VAR GapBetweenBars = 3 //两个柱子之间的间距
VAR Height_ItemLabel = 20 //类别标签高度
VAR Height_Bar = 200 //柱形最大高度
VAR FontSize = 14 //字号
VAR SpaceAboveRect = 100 //柱形上方留一定空间
VAR Height_Total = Height_ItemLabel + Height_Bar + SpaceAboveRect
VAR MaxValueCN = MAXX( ALLSELECTED( 'PVO&CNI_BI'[YEAR] ), [000 Current_year_CNPVO_value] )
VAR MaxValueGlobal = MAXX( ALLSELECTED( 'PVO&CNI_BI'[YEAR] ), [000 Current_year_GlobalPVO_value] )
VAR MaxValue = MAX(MaxValueCN, MaxValueGlobal) //取两个指标的最大值
VAR Chart ="
<!-- CN PVO 柱子 -->
<rect
x='" & (Width_Bar - (Width_BarAct * 2 + GapBetweenBars)) / 2 & "'
y='" & Height_Total - Height_ItemLabel - Height_Bar * [000 Current_year_CNPVO_value] / MaxValue & "'
height='" & Height_Bar * [000 Current_year_CNPVO_value] / MaxValue & "'
width='" & Width_BarAct & "'
fill='lightblue' />
<!-- Global PVO 柱子 -->
<rect
x='" & (Width_Bar - (Width_BarAct * 2 + GapBetweenBars)) / 2 + Width_BarAct + GapBetweenBars & "'
y='" & Height_Total - Height_ItemLabel - Height_Bar * [000 Current_year_GlobalPVO_value] / MaxValue & "'
height='" & Height_Bar * [000 Current_year_GlobalPVO_value] / MaxValue & "'
width='" & Width_BarAct & "'
fill='orange' />
<!-- 数值标签 -->
<text
x='" & (Width_Bar - (Width_BarAct * 2 + GapBetweenBars)) / 2 + Width_BarAct / 2 & "'
y='" & Height_Total - Height_ItemLabel - Height_Bar * [000 Current_year_CNPVO_value] / MaxValue - 5 & "' text-anchor='middle'
font-size='" & FontSize * 0.7 & "'
fill='deepskyblue'
>" & "</text>
<text
x='" & (Width_Bar - (Width_BarAct * 2 + GapBetweenBars)) / 2 + Width_BarAct * 1.5 + GapBetweenBars & "'
y='" & Height_Total - Height_ItemLabel - Height_Bar * [000 Current_year_GlobalPVO_value] / MaxValue - 5 & "'
text-anchor='middle'
font-size='" & FontSize * 0.7 & "'
fill='orange'
>" & "</text>
<!-- 年份标签 -->
<text
x='" & Width_Bar / 2 & "' y='" & Height_Total - FontSize / 2 & "' text-anchor='middle'
dominant-baseline='middle'
font-size='" & FontSize & "'
font-weight='bold' >" & SELECTEDVALUE('PVO&CNI_BI'[YEAR]) & "</text>"
RETURN
"<svg xmlns='http://www.w3.org/2000/svg' id='" & SELECTEDVALUE('PVO&CNI_BI'[YEAR]) & "'
width='" & Width_Bar & "'
height='" & Height_Total & "'>"
& Chart & "
</svg>"
然后是将对应数据与上面绘制的图像相结合:
Stylesheet =
// 'PVO&CNI_BI'[YEAR] ), [000 Current_year_CNPVO_value], [000 Current_year_GlobalPVO_value]
VAR n = COUNTROWS( ALLSELECTED( 'PVO&CNI_BI'[YEAR] ))
VAR Width_Bar = 80 //每个柱形空间宽度
VAR Width_BarAct_CNPVO = 35 //CNPVO柱形实际宽度
VAR Width_BarAct_Global = 21 //GlobalPVO柱形实际宽度(CNPVO宽度的60%)
VAR Width_Total = Width_Bar * n
VAR Height_ItemLabel = 20 //类别标签高度
VAR Height_Bar = 200 //柱形最大高度
VAR FontSize = 12 //字号
VAR SpaceAboveRect = 100 //柱形上方留一定空间
// 分别计算CNPVO和GlobalPVO的最大值
VAR MaxValue_CNPVO =
MAXX ( ALLSELECTED ( 'PVO&CNI_BI'[YEAR] ), [000 Current_year_CNPVO_value] )
VAR MaxValue_GlobalPVO =
MAXX ( ALLSELECTED ( 'PVO&CNI_BI'[YEAR] ), [000 Current_year_GlobalPVO_value] )// 分别计算CNPVO和GlobalPVO的总高度
VAR Height_Total_CNPVO = Height_ItemLabel + Height_Bar + SpaceAboveRect
VAR Height_Total_Global = Height_ItemLabel + Height_Bar + SpaceAboveRect
// 修正FirstValue和LastValue的计算
VAR FirstValue =
CALCULATE(
[000 Current_year_CNPVO_value],
TOPN(1, ALLSELECTED('PVO&CNI_BI'[YEAR]), 'PVO&CNI_BI'[YEAR], ASC)
)
VAR LastValue =
CALCULATE(
[000 Current_year_CNPVO_value],
TOPN(1, ALLSELECTED('PVO&CNI_BI'[YEAR]), 'PVO&CNI_BI'[YEAR], DESC)
)
VAR Gap = DIVIDE(LastValue-FirstValue,FirstValue)
// 获取第一个和最后一个柱子的中心X坐标
VAR FirstBarCenterX = Width_Bar / 4 // 调整gap线的首端位置
VAR LastBarCenterX = Width_Total - Width_Bar*(3/4) // 调整gap线的尾端位置
// 创建横向排列的柱形
VAR HorizontalBars =
CONCATENATEX(
ALLSELECTED('PVO&CNI_BI'[YEAR]),
VAR CurrentValue = [000 Current_year_CNPVO_value]
VAR GlobalValue = [000 Current_year_GlobalPVO_value]
// 分别计算CNPVO和GlobalPVO的柱子高度
VAR CNPVO_BarHeight = Height_Bar * CurrentValue / MaxValue_CNPVO
VAR Global_BarHeight = Height_Bar * GlobalValue / MaxValue_GlobalPVO
// 分别计算CNPVO和GlobalPVO的柱子Y位置
VAR CNPVO_PositionY = Height_Total_CNPVO - Height_ItemLabel - CNPVO_BarHeight
VAR Global_PositionY = Height_Total_Global - Height_ItemLabel - Global_BarHeight
VAR PositionX = (RANKX(ALLSELECTED('PVO&CNI_BI'[YEAR]), 'PVO&CNI_BI'[YEAR],,ASC) - 1) * Width_Bar
// 分别计算CNPVO和GlobalPVO的X位置
VAR CNPVO_PositionX = PositionX + (Width_Bar - Width_BarAct_CNPVO) / 2
VAR Global_PositionX = PositionX + (Width_Bar - Width_BarAct_Global) / (1.2)
VAR YearValue = SELECTEDVALUE('PVO&CNI_BI'[YEAR])
// 分别计算CNPVO和GlobalPVO柱子中心X坐标
VAR CNPVO_BarCenterX = CNPVO_PositionX + Width_BarAct_CNPVO / 2
VAR Global_BarCenterX = Global_PositionX + Width_BarAct_Global / 2
// 计算百分比标签的X坐标
VAR MonthIndex = RANKX(ALLSELECTED('PVO&CNI_BI'[YEAR]), 'PVO&CNI_BI'[YEAR],,ASC)
VAR PercentPositionX = (MonthIndex - 1) * Width_Bar + (Width_Bar - Width_BarAct_CNPVO) / 5.5
VAR PercentBarCenterX = PercentPositionX + Width_BarAct_CNPVO / 4
// 计算百分比标签的Y坐标,并向上移动10个单位
VAR PercentBarHeight = Height_Bar * CurrentValue / MaxValue_CNPVO * 0.3
VAR PercentPositionY = Height_Total_CNPVO - Height_ItemLabel - PercentBarHeight - 45 - 10
// 计算CNPVO数值标签的Y坐标,位于百分比标签下方
VAR CNPVO_ValueLabelY = PercentPositionY + 20
RETURN
// CNPVO柱形
"<rect x='" & CNPVO_PositionX & "' y='" & CNPVO_PositionY &
"' width='" & Width_BarAct_CNPVO & "' height='" & CNPVO_BarHeight &
"' fill='transparent' rx='2'/>" &
// GlobalPVO柱形
"<rect x='" & Global_PositionX & "' y='" & Global_PositionY &
"' width='" & Width_BarAct_Global & "' height='" & Global_BarHeight &
"' fill='transparent' opacity='0.6' rx='2'/>" &
// 年份标签
"<text x='" & CNPVO_BarCenterX & "' y='" & (Height_Total_CNPVO - 5) &
"' text-anchor='middle' font-size='" & FontSize * 0.8 & "'>" & YearValue & "</text>" &
// CNPVO数值标签
"<text x='" & PercentBarCenterX & "' y='" & CNPVO_ValueLabelY &
"' text-anchor='middle' font-size='" & FontSize * 0.8 & "'>" & FORMAT(CurrentValue, "#,##0.0") & "</text>" &
// GlobalPVO数值标签(使用Global柱子的中心X坐标)
"<text x='" & Global_BarCenterX & "' y='" & (Global_PositionY - 5) &
"' text-anchor='middle' font-size='" & FontSize * 0.7 & "' fill='black'>" & FORMAT(GlobalValue, "#,##0.0") & "</text>"
)
// LineBtw保持不变(百分比标签的位置计算),但需要向上移动10个单位
VAR LineBtw = ADDCOLUMNS(ALLSELECTED('PVO&CNI_BI'[YEAR]),"line",
VAR CurrentYear = SELECTEDVALUE('PVO&CNI_BI'[YEAR])
VAR PreviousYearIndex = RANKX(ALLSELECTED('PVO&CNI_BI'[YEAR]), 'PVO&CNI_BI'[YEAR],,ASC) - 1
VAR PreviousValue =
IF(PreviousYearIndex >= 1,
CALCULATE(
[000 Current_year_CNPVO_value],
WINDOW(
-1,
REL,
-1,
REL,
ALLSELECTED('PVO&CNI_BI'[YEAR]),
ORDERBY('PVO&CNI_BI'[YEAR])
)
)
)
VAR YoY = IF(ISBLANK(PreviousValue) || PreviousValue = 0, BLANK(), DIVIDE([000 Current_year_CNPVO_value] - PreviousValue, PreviousValue))
VAR MonthIndex = RANKX(ALLSELECTED('PVO&CNI_BI'[YEAR]), 'PVO&CNI_BI'[YEAR],,ASC)
VAR CurrentValue = [000 Current_year_CNPVO_value]
VAR BarHeight = Height_Bar * CurrentValue / MaxValue_CNPVO * 0.3 // YoY显示在柱子上的高度
VAR PositionX = (MonthIndex - 1) * Width_Bar + (Width_Bar - Width_BarAct_CNPVO) / 5.5
VAR BarCenterX = PositionX + Width_BarAct_CNPVO / 4 // 柱子中心X坐标
VAR PositionY = Height_Total_CNPVO - Height_ItemLabel - BarHeight - 45 - 10 // 向上移动10个单位
RETURN
IF(NOT ISBLANK(YoY),
"<text x='" & BarCenterX & "' y='" & PositionY &
"' font-size='" & FontSize * 0.8 & "' text-anchor='middle' fill='" &
IF(YoY>0,"green","red") & "'>" & FORMAT(YoY,"▲0.0%;▼0.0%;-") & "</text>"
))
// 使用CNPVO的总高度作为SVG容器的高度
VAR _CSS =
"body { background-image: url(""data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='" & Width_Total & "' height='" & Height_Total_CNPVO & "'><path d='M" & FirstBarCenterX & " " & Height_Total_CNPVO - Height_ItemLabel - Height_Bar * FirstValue / MaxValue_CNPVO & "L" & FirstBarCenterX & " " & SpaceAboveRect * 0.2 & " " & LastBarCenterX & " " & SpaceAboveRect * 0.2 & " " & LastBarCenterX & " " & Height_Total_CNPVO - Height_ItemLabel - Height_Bar * LastValue / MaxValue_CNPVO & "' fill='none' stroke='lightgrey' stroke-width='1.5' stroke-dasharray='5 2'/><path d='M" & Width_Total / 2 - Width_Bar & " " & SpaceAboveRect * 0.2 & "L" & Width_Total / 2 & " " & SpaceAboveRect * 0.1 & " " & Width_Total / 2 + Width_Bar & " " & SpaceAboveRect * 0.2 & " " & Width_Total / 2 & " " & SpaceAboveRect * 0.3 & "' fill='" & IF (Gap>0,"green","tomato") &"'/><text x='" & Width_Total / 2 & "' y='" & SpaceAboveRect * 0.25 & "' font-size='" & FontSize & "' text-anchor='middle' fill='snow'>" & FORMAT(Gap,"+0.0%;-0.0%;-") & "</text><text x='" & LastBarCenterX & "' y='" & Height_Total_CNPVO - Height_ItemLabel - Height_Bar * LastValue / MaxValue_CNPVO - 15 & "' font-size='12' text-anchor='middle' fill='grey'>▼</text>" & HorizontalBars & CONCATENATEX(LineBtw,[line]) & "</svg>"");background-repeat: no-repeat; }" & "
#htmlContent {
display: flex;
flex-wrap: nowrap;
overflow-x: auto;
white-space: nowrap;
}"
RETURN IF(n>1,_CSS)
代码部分确实有一点复杂,大家可以参照代码后面所标的注释一同理解,如果大家想要套用,只需修改表以及列的相关变量即可。
接着,我们拖入刚刚创建的第一个长度量值名称为【Bar_PVO】到Values部分,Year为表格中日期维度。
然后把第二个长度量值【Style Sheet】拖入到该视觉对象Format下的【Style Sheet】中
接着我们就得到了所想要制作的效果。