Power BI高级交互:比较两元素的差异
这看上去是一个普通的Power BI条形图:
但是,当选中任意两个条形时,可以弹出二者之间的差异对话框:
点选的顺序决定了对话框的计算结果,比如换成先点丽水店:
动画演示:
实现原理为SVG图表结合JS交互,一个度量值生成:
代码语言:javascript代码运行次数:0运行复制HTML Bar Chart 1 =
VAR SelectedStores = SELECTCOLUMNS('店铺资料', "店铺名称", '店铺资料'[店铺名称], "销售业绩", [M.销售业绩])
VAR MaxValue = MAXX(SelectedStores, [销售业绩])
VAR StoreCount = COUNTROWS(SelectedStores)
VAR ChartHeight = StoreCount * 45
VAR BarHeight = 30
VAR Margin = 15
VAR Width = 1000
VAR LeftMargin = 150
VAR RightMargin = 100
RETURN
"
<div id='chartContainer' style='width:100%; overflow:auto;'>
<svg id='barChartSVG' width='" & Width & "' height='" & ChartHeight & "' xmlns=''>
" &
CONCATENATEX(
SelectedStores,
"
<g class='bar-group' transform='translate(0, " & (RANKX(SelectedStores, [销售业绩], , DESC) - 1) * (BarHeight + Margin) & ")'
data-store='" & [店铺名称] & "'
data-value='" & [销售业绩] & "'
data-bar-height='" & BarHeight & "'
data-margin='" & Margin & "'>
<!-- 透明点击区域 -->
<rect
class='click-area'
x='0'
y='0'
width='" & Width & "'
height='" & BarHeight + Margin & "'
fill='transparent'
/>
<!-- 实际条形 -->
<rect
class='bar'
x='" & LeftMargin & "'
y='" & (Margin/2) & "'
width='" & DIVIDE([销售业绩], MaxValue, 0) * (Width - LeftMargin - RightMargin) & "'
height='" & BarHeight & "'
fill='deepskyblue'
/>
<!-- 店铺名称标签 -->
<text
x='" & LeftMargin - 10 & "'
y='" & (Margin/2) + BarHeight/2 & "'
text-anchor='end'
dominant-baseline='middle'
font-size='14px'
font-family='Arial'>
" & [店铺名称] & "
</text>
<!-- 数值标签 -->
<text
x='" & LeftMargin + DIVIDE([销售业绩], MaxValue, 0) * (Width - LeftMargin - RightMargin) + 10 & "'
y='" & (Margin/2) + BarHeight/2 & "'
text-anchor='start'
dominant-baseline='middle'
font-size='12px'
font-family='Arial'>
" & FORMAT([销售业绩], "#,##0") & "
</text>
</g>
",
UNICHAR(10)
) & "
</svg>
</div>
<script>
(function() {
let selectedBars = [];
function initBarChart() {
const svg = document.getElementById('barChartSVG');
if (!svg) {
setTimeout(initBarChart, 100);
return;
}
// 清除旧的事件监听器
svg.replaceWith(svg.cloneNode(true));
const newSvg = document.getElementById('barChartSVG');
newSvg.addEventListener('click', function(event) {
event.preventDefault();
let target = event.target;
while (target && !target.classList.contains('bar-group')) {
target = target.parentElement;
}
if (!target) return;
const store = target.getAttribute('data-store');
const value = parseFloat(target.getAttribute('data-value'));
const barHeight = parseFloat(target.getAttribute('data-bar-height'));
const margin = parseFloat(target.getAttribute('data-margin'));
const bar = target.querySelector('.bar');
const index = selectedBars.findIndex(b => b.store === store);
if (index === -1) {
if (selectedBars.length >= 2) {
const firstBar = selectedBars[0].element;
firstBar.setAttribute('fill', 'deepskyblue');
selectedBars.shift();
clearComparison(newSvg);
}
selectedBars.push({
store,
value,
element: bar,
group: target,
barHeight,
margin
});
bar.setAttribute('fill', '#FF5722');
if (selectedBars.length === 2) {
drawComparison(newSvg, selectedBars[0], selectedBars[1]);
}
} else {
selectedBars.splice(index, 1);
bar.setAttribute('fill', 'deepskyblue');
clearComparison(newSvg);
}
});
}
function clearComparison(svg) {
const existingComparison = svg.querySelector('parison-bubble');
if (existingComparison) {
existingComparison.remove();
}
}
function drawComparison(svg, bar1, bar2) {
clearComparison(svg);
const diff = bar1.value - bar2.value;
const diffPercent = (diff / bar2.value) * 100;
// 计算画布中心位置
const svgWidth = parseFloat(svg.getAttribute('width'));
const svgHeight = parseFloat(svg.getAttribute('height'));
const centerX = svgWidth / 2;
const centerY = svgHeight / 2;
const bubbleGroup = document.createElementNS('', 'g');
bubbleGroup.setAttribute('class', 'comparison-bubble');
const bubble = document.createElementNS('', 'rect');
const bubbleWidth = 200;
const bubbleHeight = 100;
const cornerRadius = 5;
bubble.setAttribute('x', centerX - bubbleWidth/2);
bubble.setAttribute('y', centerY - bubbleHeight/2);
bubble.setAttribute('width', bubbleWidth);
bubble.setAttribute('height', bubbleHeight);
bubble.setAttribute('rx', cornerRadius);
bubble.setAttribute('ry', cornerRadius);
bubble.setAttribute('fill', 'brown');
bubble.setAttribute('stroke', '#3F51B5');
bubble.setAttribute('stroke-width', '1');
const foreignObject = document.createElementNS('', 'foreignObject');
foreignObject.setAttribute('x', centerX - bubbleWidth/2 + 10);
foreignObject.setAttribute('y', centerY - bubbleHeight/2 + 10);
foreignObject.setAttribute('width', bubbleWidth - 20);
foreignObject.setAttribute('height', bubbleHeight - 20);
const div = document.createElementNS('', 'div');
div.style.color = 'white';
div.style.fontFamily = 'Arial';
div.style.fontSize = '18px';
div.style.lineHeight = '1.4';
div.style.wordWrap = 'break-word';
div.style.whiteSpace = 'normal';
div.style.textAlign = 'center';
div.textContent = `${bar1.store}比${bar2.store} ${diff >= 0 ? '多' : '少'}${Math.abs(Math.round(diff)).toLocaleString()} (${diffPercent.toFixed(1)}%)`;
foreignObject.appendChild(div);
bubbleGroup.appendChild(bubble);
bubbleGroup.appendChild(foreignObject);
svg.appendChild(bubbleGroup);
}
initBarChart();
})();
</script>
"
把度量值中的维度和指标替换为你模型中的数据,放入HTML Content视觉对象使用。
类似的原理可以扩展应用到其他图表类型,比如柱形图:
折线图:
发布者:admin,转转请注明出处:http://www.yc00.com/web/1747987658a4715468.html
评论列表(0条)