Wykres hantlowy w Power BI (ang. dumbbell chart, znany również jako wykres DNA lub sztangowy) to jedno z najbardziej czytelnych rozwiązań wizualnych do porównywania dwóch punktów danych w ramach różnych kategorii. Standardowe wizualizacje w Power BI często zmuszają do korzystania z przeładowanych, zgrupowanych wykresów słupkowych lub osobnych tabel przestawnych, aby pokazać odchylenia między bieżącym a poprzednim okresem.
Dzięki wykorzystaniu dodatku Deneb i języka Vega-Lite możesz stworzyć w Power BI w pełni spersonalizowany wykres hantlowy. Oferuje on automatyczny ranking Top 10, bezkolizyjne etykiety danych, czytelne dla człowieka jednostki liczb (tys. i mln) oraz wbudowaną kolumnę odchyleń (DIFF). ✨

Dlaczego warto wybrać wykres hantlowy w Power BI? 💡
- 🎯 Zero chaosu: Wyświetla bieżący rok (CY) i poprzedni rok (PY) na jednej poziomej osi.
- 📊 Błyskawiczny wgląd w odchylenia: Dedykowany, kontrastowy pasek DIFF tuż obok wykresu.
- 🛡️ Inteligentne unikanie nakładania się tekstu: Gdy punkty leżą zbyt blisko siebie, etykieta poprzedniego okresu dynamicznie się ukrywa, eliminując ryzyko nachodzenia na siebie liczb.
- 🔢 Automatyczne formatowanie liczb: Skrótowe wartości zaokrąglone do pełnych jednostek (tys., mln) bez zbędnych miejsc po przecinku.
- 🔄 W 100% gotowy do ponownego użycia szablon: Wystarczy przeciągnąć 3 pola (Kategoria, Wartość bieżąca, Wartość poprzednia), a wizualizacja skonfiguruje się sama w ułamku sekundy.
Instrukcja wdrożenia krok po kroku 🛠️
Krok 1: Dodaj wizualizację Deneb 📦
- W programie Power BI Desktop przejdź do panelu Wizualizacje (Visualizations).
- Kliknij ikonę wielokropka (…) i wybierz Pobierz więcej wizualizacji (Get more visuals).
- Wyszukaj Deneb: Declarative Visualization in Power BI i kliknij Dodaj (Add).
Krok 2: Przypisz i zmień nazwy pól 🏷️
- Dodaj wizualizację Deneb do kanwy raportu.
- Przeciągnij swoje 3 pola do sekcji Wartości (Values) wizualizacji.
- Zmień nazwy pól bezpośrednio w wizualizacji (kliknij prawym przyciskiem myszy na pole –>Zmień nazwę dla tej wizualizacji / Rename for this visual):
- Kolumna z kategorią –>
Category - Główna/bieżąca miara –>
Current Value - Miara porównawcza/poprzednia –>
Prev Value
- Kolumna z kategorią –>
Krok 3: Wklej specyfikację Vega-Lite 💻
- Kliknij menu … w prawym górnym rogu wizualizacji i wybierz Edytuj (Edit).
- W zakładce specyfikacji zastąp cały dotychczasowy kod poniższym kodem JSON:
JSON
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"data": {"name": "dataset"},
"spacing": 1,
"padding": {"top": 22, "left": 5, "right": 10, "bottom": 5},
"config": {
"font": "Segoe UI, sans-serif"
},
"transform": [
{
"filter": "datum['Category'] != null && datum['Category'] != ''"
},
{
"window": [{"op": "row_number", "as": "rank"}],
"sort": [{"field": "Current Value", "order": "descending"}]
},
{
"filter": "datum.rank <= 10"
},
{
"calculate": "datum['Current Value'] - datum['Prev Value']",
"as": "Diff"
},
{
"calculate": "(datum['Diff'] > 0 ? '▲ +' : datum['Diff'] < 0 ? '▼ -' : '') + (abs(datum['Diff']) >= 1000000 ? format(abs(datum['Diff']) / 1000000, ',.0f') + ' mln' : abs(datum['Diff']) >= 1000 ? format(abs(datum['Diff']) / 1000, ',.0f') + ' k' : format(abs(datum['Diff']), ',.0f'))",
"as": "Diff_Formatted"
},
{
"calculate": "abs(datum['Current Value']) >= 1000000 ? format(datum['Current Value'] / 1000000, ',.0f') + ' mln' : abs(datum['Current Value']) >= 1000 ? format(datum['Current Value'] / 1000, ',.0f') + ' k' : format(datum['Current Value'], ',.0f')",
"as": "CY_Formatted"
},
{
"calculate": "abs(datum['Prev Value']) >= 1000000 ? format(datum['Prev Value'] / 1000000, ',.0f') + ' mln' : abs(datum['Prev Value']) >= 1000 ? format(datum['Prev Value'] / 1000, ',.0f') + ' k' : format(datum['Prev Value'], ',.0f')",
"as": "PY_Formatted_Base"
},
{
"calculate": "datum['Current Value'] != 0 && abs(datum['Diff']) / abs(datum['Current Value']) < 0.15 ? '' : datum['PY_Formatted_Base']",
"as": "PY_Formatted"
}
],
"hconcat": [
{
"width": 380,
"encoding": {
"y": {
"field": "Category",
"type": "nominal",
"scale": {
"step": 44,
"paddingOuter": 1.0
},
"axis": {
"title": null,
"domain": false,
"ticks": false,
"labelPadding": 12,
"labelFontSize": 11,
"labelFontWeight": 300,
"labelColor": "#333333"
},
"sort": {"field": "Current Value", "op": "max", "order": "descending"}
}
},
"layer": [
{
"name": "PASEK_LACZACY",
"mark": {
"type": "rule",
"color": "#E5E5E5",
"strokeWidth": 2
},
"encoding": {
"x": {
"field": "Prev Value",
"type": "quantitative"
},
"x2": {
"field": "Current Value"
}
}
},
{
"name": "KULKA_PY",
"mark": {
"type": "circle",
"size": 110
},
"encoding": {
"x": {
"field": "Prev Value",
"type": "quantitative",
"axis": {
"grid": true,
"gridColor": "#F2F2F2",
"domain": false,
"ticks": false,
"title": null,
"labelFontSize": 9,
"labelFontWeight": 300,
"labelColor": "#888888",
"format": "~s"
}
},
"color": {
"datum": "Previous Value",
"type": "nominal",
"scale": {
"domain": ["Current Value", "Previous Value"],
"range": ["#ED8A18", "#8C8C8C"]
},
"legend": {
"orient": "top",
"direction": "horizontal",
"title": null,
"labelFontSize": 11,
"labelFontWeight": 300,
"symbolSize": 80,
"padding": 5,
"offset": 12
}
}
}
},
{
"name": "ETYKIETA_PY",
"mark": {
"type": "text",
"dy": 11,
"color": "#666666",
"fontSize": 8.5,
"fontWeight": 300,
"font": "Segoe UI"
},
"encoding": {
"x": {"field": "Prev Value", "type": "quantitative"},
"text": {"field": "PY_Formatted"}
}
},
{
"name": "KULKA_CY",
"mark": {
"type": "circle",
"size": 110
},
"encoding": {
"x": {"field": "Current Value", "type": "quantitative"},
"color": {
"datum": "Current Value",
"type": "nominal"
}
}
},
{
"name": "ETYKIETA_CY",
"mark": {
"type": "text",
"dy": -11,
"color": "#D37300",
"fontSize": 8.5,
"fontWeight": 300,
"font": "Segoe UI"
},
"encoding": {
"x": {"field": "Current Value", "type": "quantitative"},
"text": {"field": "CY_Formatted"}
}
}
]
},
{
"width": 95,
"title": {
"text": "DIFF",
"fontSize": 10,
"fontWeight": 400,
"color": "#555555"
},
"encoding": {
"y": {
"field": "Category",
"type": "nominal",
"axis": null,
"scale": {
"step": 44,
"paddingOuter": 1.0
},
"sort": {"field": "Current Value", "op": "max", "order": "descending"}
}
},
"layer": [
{
"name": "TLO_DIFF",
"mark": {
"type": "rect",
"color": "#F4F3EF"
}
},
{
"name": "WARTOSC_DIFF",
"mark": {
"type": "text",
"fontWeight": 400,
"fontSize": 10,
"font": "Segoe UI"
},
"encoding": {
"text": {"field": "Diff_Formatted"},
"color": {
"condition": [
{
"test": "datum['Diff'] > 0",
"value": "#137333"
},
{
"test": "datum['Diff'] < 0",
"value": "#C5221F"
}
],
"value": "#666666"
}
}
}
]
}
],
"resolve": {
"scale": {
"y": "shared"
}
}
}
- Naciśnij Zastosuj (Apply / Ctrl + Enter) i kliknij Wróć do raportu (Back to report). ✅
Personalizacja wykresu ⚙️
- 🔝 Zmień limit Top N: Zmodyfikuj
"datum.rank <= 10"w sekcjitransform(np. ustaw wartość na5lub15). - 📏 Dostosuj odstępy między wierszami: Zmień wartość w
"scale": {"step": 36}, aby zagęścić lub rozsunąć wiersze. - 🎨 Dopasuj paletę barw: Zaktualizuj kody kolorów HEX w
"range": ["#ED8A18", "#8C8C8C"], aby dopasować je do identyfikacji wizualnej swojej firmy.
Ten spersonalizowany szablon wykresu hantlowego w Power BI przekształca rutynowe porównania okres do okresu w profesjonalne wizualizacje gotowe dla kadry zarządzającej – bez konieczności ciągłej obsługi. Skopiuj kod JSON, przypisz swoje miary i błyskawicznie przenieś design swoich dashboardów na wyższy poziom. 📈🔥
