一、引言
今天的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】中
接著我們就得到了所想要制作的效果。