Power BI Financial Dashboard
Transform raw financial data into an interactive, boardroom-ready dashboard that tells the story of a company's financial health at a glance.
Learning Objectives
- Connect Power BI Desktop to financial data sources and build a structured data model
- Write DAX measures for key financial metrics — revenue growth, margins, ROIC, leverage, and liquidity ratios
- Design interactive KPI cards, trend charts, decomposition trees, and ratio comparison visuals
- Apply executive dashboard design principles: layout hierarchy, consistent color coding, and narrative flow
- Publish a complete financial analysis dashboard and share it for stakeholder review
- No Power BI? No problem: Build a functionally equivalent interactive dashboard in Excel using PivotTables, PivotCharts, and Slicers
The deliverable of this session is an interactive financial dashboard — the tool is secondary. The primary path is Power BI Desktop (free, Windows). On macOS/Linux, use Google Looker Studio (free, browser — Section 6.9 maps every step). And if you have neither but do have Excel: the green Excel Alt boxes show how to build a genuinely interactive dashboard with PivotTables + PivotCharts + Slicers — clicking a slicer filters every chart, just like Power BI. All three produce the same analysis; only the click-paths differ.
🔍 Opening Challenge — The CFO's Question
6.1 Why Power BI for Financial Dashboards?
In Chapters 3–5, you built Python pipelines that compute value drivers, ratios, and ROIC. These pipelines are powerful — but their output is DataFrames and static charts. When you present to a CFO, a board, or an investment committee, you need something more: an interactive, self-service dashboard where stakeholders can explore the data themselves, filter by year or segment, and drill into the numbers behind the headline.
Microsoft Power BI is the tool of choice for this task for several reasons:
| Capability | Why It Matters for Valuation |
|---|---|
| Data connectivity | Import from Excel, CSV, databases, and Python DataFrames. Your Python output feeds directly into Power BI. |
| DAX measures | Write reusable financial calculations (YoY growth, margins, ratios) that respond dynamically to filters and slicers. |
| Interactive visuals | KPI cards, line charts, waterfall charts, decomposition trees — purpose-built for financial storytelling. |
| Cross-filtering | Click on a year, a segment, or a company, and every visual on the page updates instantly. This is impossible in static reports. |
| Cloud sharing | Publish to Power BI Service and share a live link. Stakeholders interact with the dashboard in their browser — no software installation needed. |
| Free + Pro tiers | Power BI Desktop is free. Microsoft 365 A1 (education) includes Power BI Pro for publishing. This course uses the free Desktop tier. |
Revisit your hook answers. Which of the six capabilities in the table above would have answered the CFO's "exclude the two smallest companies" question live? Identify at least two, and describe in one sentence each how they combine to produce the five-second answer.
Reveal
Slicers + cross-filtering: untick the two smallest companies in the Company slicer — every KPI card, trend, and comparison chart recomputes instantly, live, in the meeting. No new slides, no "I'll get back to you." That interactivity is the entire reason dashboards exist.
Excel has had Power BI's core engine — PivotTables — for decades, and slicers make it interactive. The same CSV files this session generates power it:
- Import: open
ratio_data.csvin Excel → select any cell in the data → Insert → PivotTable → New Worksheet. - First visual (the ROIC comparison): drag Company to Rows, Value to Values (set to Average, not Sum!), filter Ratio to "ROIC (%)". Then Insert → PivotChart → Clustered Bar — the chart and table are now linked.
- The interactivity: click inside the PivotTable → PivotTable Analyze → Insert Slicer → tick Company and Year. Every slicer click now filters the PivotTable and its chart — the CFO question answered live, in Excel.
- Build the page: repeat for the margin trend (Rows = Year, filter Ratio = "Operating Margin (%)", Line PivotChart) and KPI summary (a small PivotTable filtered to the latest year). Arrange charts + slicers on one sheet following the Section 6.6 Z-pattern.
- One slicer, many charts:
Ctrl-click to select multiple PivotTables → PivotTable Analyze → Report Connections (or right-click the slicer) → tick all PivotTables. Now ONE Company slicer drives every chart — true cross-filtering, exactly like Power BI.
Excel vs Power BI honestly: Excel wins on familiarity and zero installation; Power BI wins on DAX measures, decomposition trees, cloud sharing, and polish. For this course's deliverable, both are fully acceptable — the analytical content is judged, not the tool.
6.2 Setting Up Power BI Desktop
6.2.1 Installation
- Download Power BI Desktop from powerbi.microsoft.com/en-us/desktop/ (free).
- Alternatively, install from the Microsoft Store (search "Power BI Desktop").
- Launch Power BI Desktop. You will see a blank canvas with three views on the left: Report (visuals), Data (tables), and Model (relationships).
6.2.2 The Power BI Workflow
Every Power BI project follows a four-step workflow. Understanding this workflow before you start will prevent hours of confusion:
🔗 Before You Move On — Quick Checks
A classmate proudly shows you a beautiful dashboard — built before checking data types. The Year column loaded as Text. What will break?
Reveal
True or False: If your dashboard visuals look wrong, the fix is usually in the Report view formatting options.
Reveal
6.3 Preparing Your Financial Data for Power BI
Power BI works best with clean, structured, unpivoted data. The financial DataFrames we generated in Chapters 3–5 are wide-format (years as columns, metrics as rows). Power BI prefers long/tall format (one row per metric per year). Let us prepare the data correctly.
6.3.1 The Target Schema
Your Power BI data model should have this structure for a financial dashboard:
| Table Name | Columns | Description |
|---|---|---|
| Financials | Year, Company, Metric, Value | The main fact table — one row per metric per company per year. Metrics include Revenue, COGS, EBIT, Net Income, Total Assets, etc. |
| Ratios | Year, Company, Ratio, Value | Pre-computed ratios — Gross Margin, Operating Margin, ROIC, Current Ratio, D/E, etc. |
| Company | CompanyID, CompanyName, Sector, Ticker, MarketCap | Dimension table with company attributes for filtering. |
| Date | Date, Year, FiscalYear | A date dimension table for time intelligence functions. |
6.3.2 Python Script: Generate Power BI-Ready Financial Data
Run this Python script to fetch financial data for multiple Indian companies and export it in Power BI-friendly format. You must run this script and save the CSV files before you start the Power BI section.
"""
Generate Power BI-ready financial datasets for Indian companies.
Run this script once. It produces two CSV files:
- financial_data.csv → Raw financial statement line items (long format)
- ratio_data.csv → Pre-computed financial ratios (long format)
Import both into Power BI Desktop as your data source.
"""
import yfinance as yf
import pandas as pd
import numpy as np
import time
# ============================================================
# CONFIGURATION: Choose your companies
# ============================================================
COMPANIES = {
'TCS.NS': {'Name': 'Tata Consultancy Services', 'Sector': 'IT Services'},
'INFY.NS': {'Name': 'Infosys', 'Sector': 'IT Services'},
'WIPRO.NS': {'Name': 'Wipro', 'Sector': 'IT Services'},
'HCLTECH.NS': {'Name': 'HCL Technologies', 'Sector': 'IT Services'},
'TECHM.NS': {'Name': 'Tech Mahindra', 'Sector': 'IT Services'},
}
# Alternatively, for a cross-sector comparison:
# COMPANIES = {
# 'TCS.NS': {'Name': 'TCS', 'Sector': 'IT'},
# 'ASIANPAINT.NS': {'Name': 'Asian Paints', 'Sector': 'Consumer'},
# 'RELIANCE.NS': {'Name': 'Reliance Industries', 'Sector': 'Conglomerate'},
# 'HDFCBANK.NS': {'Name': 'HDFC Bank', 'Sector': 'Banking'},
# 'SUNPHARMA.NS': {'Name': 'Sun Pharma', 'Sector': 'Pharma'},
# }
# ============================================================
# DATA EXTRACTION
# ============================================================
def safe_get(df, candidates):
"""Try multiple field names, return first match."""
if isinstance(candidates, str):
candidates = [candidates]
for c in candidates:
if c in df.index:
return df.loc[c]
return None
financial_rows = []
ratio_rows = []
for ticker, meta in COMPANIES.items():
print(f"Fetching {meta['Name']} ({ticker})...")
try:
t = yf.Ticker(ticker)
info = t.info
is_df = t.financials
bs_df = t.balance_sheet
cf_df = t.cashflow
# --- Raw Financial Metrics (long format) ---
mappings = {
'Total Revenue': ['Total Revenue', 'Revenue'],
'Cost of Revenue': ['Cost Of Goods Sold', 'Cost of Revenue'],
'Gross Profit': ['Gross Profit'],
'Operating Income': ['Operating Income', 'EBIT'],
'EBITDA': ['EBITDA'],
'Net Income': ['Net Income', 'Net Income Common Stockholders'],
'Total Assets': ['Total Assets'],
'Current Assets': ['Current Assets', 'Total Current Assets'],
'Current Liabilities': ['Current Liabilities', 'Total Current Liabilities'],
'Total Debt': ['Total Debt', 'Long Term Debt'],
'Shareholders Equity': ['Total Equity Gross Minority Interest',
'Stockholders Equity', 'Total Equity'],
'Cash and Equivalents': ['Cash And Cash Equivalents',
'Cash Cash Equivalents And Short Term Investments'],
'Operating Cash Flow': ['Operating Cash Flow'],
'Capital Expenditure': ['Capital Expenditure', 'Capital Expenditure Reported'],
'Accounts Receivable': ['Accounts Receivable', 'Net Receivables'],
'Inventory': ['Inventory', 'Inventories'],
}
for metric_name, field_candidates in mappings.items():
val = safe_get(is_df, field_candidates)
if val is None:
val = safe_get(bs_df, field_candidates)
if val is None:
val = safe_get(cf_df, field_candidates)
if val is not None:
for fiscal_year, value in val.items():
if pd.notna(value) and value != 0:
financial_rows.append({
'Company': meta['Name'],
'Ticker': ticker.replace('.NS', ''),
'Sector': meta['Sector'],
'Year': int(str(fiscal_year).split('-')[0]),
'Metric': metric_name,
'Value': round(value / 1e7, 2) # Convert to Rs. Crore
})
# --- Pre-computed Ratios (long format) ---
rev = safe_get(is_df, ['Total Revenue', 'Revenue'])
gp = safe_get(is_df, ['Gross Profit'])
ebit = safe_get(is_df, ['Operating Income', 'EBIT'])
ni = safe_get(is_df, ['Net Income', 'Net Income Common Stockholders'])
ebitda = safe_get(is_df, ['EBITDA'])
ca = safe_get(bs_df, ['Current Assets', 'Total Current Assets'])
cl = safe_get(bs_df, ['Current Liabilities', 'Total Current Liabilities'])
debt = safe_get(bs_df, ['Total Debt', 'Long Term Debt'])
eq = safe_get(bs_df, ['Total Equity Gross Minority Interest',
'Stockholders Equity', 'Total Equity'])
cash = safe_get(bs_df, ['Cash And Cash Equivalents',
'Cash Cash Equivalents And Short Term Investments'])
ta = safe_get(bs_df, ['Total Assets'])
ocf = safe_get(cf_df, ['Operating Cash Flow'])
capex = safe_get(cf_df, ['Capital Expenditure', 'Capital Expenditure Reported'])
for year_idx in is_df.columns:
fy = int(str(year_idx).split('-')[0])
try:
if rev is not None and rev.get(year_idx):
r = rev[year_idx]
# Margins
if gp is not None and gp.get(year_idx):
ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
'Sector': meta['Sector'], 'Year': fy,
'Ratio': 'Gross Margin (%)',
'Value': round((gp[year_idx]/r)*100, 2)})
if ebit is not None and ebit.get(year_idx):
ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
'Sector': meta['Sector'], 'Year': fy,
'Ratio': 'Operating Margin (%)',
'Value': round((ebit[year_idx]/r)*100, 2)})
if ni is not None and ni.get(year_idx):
ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
'Sector': meta['Sector'], 'Year': fy,
'Ratio': 'Net Margin (%)',
'Value': round((ni[year_idx]/r)*100, 2)})
if ebitda is not None and ebitda.get(year_idx):
ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
'Sector': meta['Sector'], 'Year': fy,
'Ratio': 'EBITDA Margin (%)',
'Value': round((ebitda[year_idx]/r)*100, 2)})
# Revenue Growth
prev_year = str(fy - 1)
for y2 in is_df.columns:
if str(y2).startswith(prev_year):
prev_rev = rev[y2]
if prev_rev and prev_rev != 0:
growth = ((r - prev_rev) / prev_rev) * 100
ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
'Sector': meta['Sector'], 'Year': fy,
'Ratio': 'Revenue Growth (%)',
'Value': round(growth, 2)})
break
# Ratios from balance sheet
if ca is not None and cl is not None and ca.get(year_idx) and cl.get(year_idx):
cr = (ca[year_idx] / cl[year_idx])
ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
'Sector': meta['Sector'], 'Year': fy,
'Ratio': 'Current Ratio', 'Value': round(cr, 2)})
if debt is not None and eq is not None and debt.get(year_idx) and eq.get(year_idx):
de = debt[year_idx] / eq[year_idx]
ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
'Sector': meta['Sector'], 'Year': fy,
'Ratio': 'Debt-to-Equity', 'Value': round(de, 2)})
# ROIC (preliminary)
if ebit is not None and eq is not None and debt is not None and cash is not None:
if all(x.get(year_idx) for x in [ebit, eq, debt, cash]):
nopat = ebit[year_idx] * 0.75
ic = eq[year_idx] + debt[year_idx] - cash[year_idx]
if ic > 0:
roic = (nopat / ic) * 100
ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
'Sector': meta['Sector'], 'Year': fy,
'Ratio': 'ROIC (%)', 'Value': round(roic, 2)})
# ROE
if ni is not None and eq is not None and ni.get(year_idx) and eq.get(year_idx):
roe = (ni[year_idx] / eq[year_idx]) * 100
ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
'Sector': meta['Sector'], 'Year': fy,
'Ratio': 'ROE (%)', 'Value': round(roe, 2)})
# Asset Turnover
if rev is not None and ta is not None and rev.get(year_idx) and ta.get(year_idx):
at = rev[year_idx] / ta[year_idx]
ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
'Sector': meta['Sector'], 'Year': fy,
'Ratio': 'Asset Turnover', 'Value': round(at, 2)})
# FCF Yield
mkt_cap = info.get('marketCap')
if ocf is not None and capex is not None and mkt_cap:
if ocf.get(year_idx) and capex.get(year_idx):
fcf = ocf[year_idx] + capex[year_idx]
fcf_yield = (fcf / mkt_cap) * 100
ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
'Sector': meta['Sector'], 'Year': fy,
'Ratio': 'FCF Yield (%)', 'Value': round(fcf_yield, 2)})
except Exception:
pass # Skip individual year/ratio failures
print(f" ✓ {meta['Name']} — {len([r for r in financial_rows if r['Company']==meta['Name']])} financial rows, "
f"{len([r for r in ratio_rows if r['Company']==meta['Name']])} ratio rows")
except Exception as e:
print(f" ✗ {meta['Name']}: {e}")
time.sleep(1.5)
# Save to CSV
df_fin = pd.DataFrame(financial_rows)
df_rat = pd.DataFrame(ratio_rows)
df_fin.to_csv('financial_data.csv', index=False)
df_rat.to_csv('ratio_data.csv', index=False)
print(f"\n=== DONE ===")
print(f"financial_data.csv: {len(df_fin)} rows, {df_fin['Company'].nunique()} companies, "
f"{df_fin['Year'].min()}-{df_fin['Year'].max()}")
print(f"ratio_data.csv: {len(df_rat)} rows, {df_rat['Company'].nunique()} companies")
print(f"\nUnique Metrics: {sorted(df_fin['Metric'].unique())}")
print(f"Unique Ratios: {sorted(df_rat['Ratio'].unique())}")
print(f"\nImport both CSV files into Power BI Desktop to begin the dashboard build.")
6.4 Building the Data Model in Power BI
With your CSV files ready, let us build the data model — the foundation of your entire dashboard.
6.4.1 Import Data
financial_data.csv and click Load. Repeat for ratio_data.csv.financial_data table. Confirm: Year is Whole Number, Value is Decimal Number, and Company, Sector, Ticker, Metric are Text. If any column has the wrong type, click the column header, go to Column Tools ribbon, and change the Data Type.6.4.2 Create the Date Table (Essential for Time Intelligence)
DAX time intelligence functions (YEAR-OVER-YEAR growth, running totals, moving averages) require a proper date table. Create one now:
-- 1. Go to Home → New Table (or Modeling → New Table)
-- 2. Paste this DAX expression:
DateTable =
VAR MinYear = MIN(ratio_data[Year])
VAR MaxYear = MAX(ratio_data[Year])
RETURN
ADDCOLUMNS(
CALENDAR(DATE(MinYear, 4, 1), DATE(MaxYear, 3, 31)),
"FiscalYear",
IF(MONTH([Date]) >= 4,
YEAR([Date]),
YEAR([Date]) - 1
),
"MonthNum", MONTH([Date]),
"MonthName", FORMAT([Date], "MMM"),
"Quarter", "Q" & FORMAT([Date], "Q"),
"FiscalQuarter",
"FY" & RIGHT(
IF(MONTH([Date]) >= 4, YEAR([Date]), YEAR([Date]) - 1), 2
) & "-Q" & FORMAT(
IF(MONTH([Date]) >= 4,
INT((MONTH([Date]) - 4) / 3) + 1,
INT((MONTH([Date]) + 8) / 3) + 1
), "0"
)
)
-- 3. Mark as date table: Select DateTable → Table Tools → Mark as Date Table → choose [Date] column.
This creates a date table aligned to the Indian fiscal year (April–March). If your data uses a January–December calendar year, simplify by removing the FiscalYear logic and using the plain Year column.
6.4.3 Create Relationships
In Model view, drag the Year column from ratio_data to the FiscalYear column in DateTable. This creates a one-to-many relationship (one DateTable row → many ratio_data rows for that year).
6.5 Writing DAX Measures for Financial Analysis
DAX (Data Analysis Expressions) is the formula language of Power BI. Measures are DAX formulas that compute values dynamically based on the current filter context. When a user clicks on "TCS" in a slicer, all measures automatically recompute for TCS.
6.5.1 KPI Measures
Right-click the ratio_data table in the Data pane and select New Measure. Create each of these:
| Measure Name | DAX Formula | What It Shows |
|---|---|---|
Latest Revenue Growth |
VAR LatestYear = MAX(ratio_data[Year])RETURN CALCULATE(MAX(ratio_data[Value]), ratio_data[Ratio] = "Revenue Growth (%)", ratio_data[Year] = LatestYear)
|
Most recent year's revenue growth rate |
Latest Operating Margin |
Same pattern, replace Ratio filter with "Operating Margin (%)" |
Most recent operating margin |
Latest ROIC |
Same pattern, replace Ratio filter with "ROIC (%)" |
Most recent ROIC |
Latest Net Margin |
Same pattern, replace Ratio filter with "Net Margin (%)" |
Most recent net margin |
Latest D/E |
Same pattern, replace Ratio filter with "Debt-to-Equity" |
Most recent debt-to-equity |
The pattern for each KPI measure is identical — only the ratio name changes. Here is the full DAX for one:
Latest ROIC =
VAR LatestYear = MAX(ratio_data[Year])
RETURN
CALCULATE(
MAX(ratio_data[Value]),
ratio_data[Ratio] = "ROIC (%)",
ratio_data[Year] = LatestYear
)
6.5.2 Trend Measures
For line charts that show a metric over time, create this simple aggregation measure:
Operating Margin =
CALCULATE(
MAX(ratio_data[Value]),
ratio_data[Ratio] = "Operating Margin (%)"
)
Create similar measures for Gross Margin, Net Margin, ROIC, ROE, Revenue Growth, and Current Ratio. You will need one trend measure per ratio you want to chart.
6.5.3 Conditional Color Measure
For KPI cards that turn green or red based on performance:
ROIC Color =
VAR roic = [Latest ROIC]
RETURN
SWITCH(
TRUE(),
roic >= 25, "#00C9A7", -- Green: Excellent
roic >= 15, "#6C8CFF", -- Blue: Good
roic >= 10, "#F0A040", -- Orange: Adequate
roic < 10, "#E0556A", -- Red: Poor
"#A0A0B8" -- Grey: No data
)
Part 1 — Predict (1 min, solo): Before typing anything, what will [Latest ROIC] return if the slicer selects two companies — TCS (ROIC 45%) and Infosys (ROIC 38%)? One number or two? Whose?
Part 2 — Debug (pairs): A classmate's trend chart shows Operating Margin at 10x the expected value (e.g., 250 instead of 25). Their measure:
Operating Margin =
SUM(ratio_data[Value])
Three questions: (a) Why is the number absurd? (b) Why did we use MAX with a RATIO = filter instead of SUM? (c) Write the corrected measure from memory.
Reveal answers
Part 1: MAX over the filtered rows — it returns the higher of the two (45%), silently. A KPI card assumes one context; for multi-select, you'd want AVERAGEX or "don't summarize" — know what your aggregation does when a filter returns several rows. Part 2: (a) SUM adds every ratio row in scope — margins, growth, D/E, everything — across all years: garbage. (b) The filter ratio_data[Ratio] = "Operating Margin (%)" isolates one metric; MAX then takes that (single) year's value. (c) The CALCULATE(MAX(...), Ratio = "Operating Margin (%)") pattern from 6.5.2 above.
6.6 Building the Dashboard Visuals
Now we assemble the visuals. Switch to Report view. The canvas is where your dashboard comes to life.
6.6.1 Dashboard Layout: The Grid
An executive dashboard follows a Z-pattern reading flow: top-left (most important) → top-right → bottom-left → bottom-right. Arrange your canvas as follows:
| Position | Visual Type | Content |
|---|---|---|
| Top Row — Left | 4 KPI Cards | Latest Revenue Growth %, Operating Margin %, ROIC %, Net Margin % |
| Top Row — Right | Slicer (dropdown) | Company selector — allows filtering the entire dashboard to one company |
| Middle Row — Left | Line Chart | Margin trends over time (Gross, Operating, Net) — multi-series |
| Middle Row — Right | Clustered Bar Chart | ROIC by company comparison |
| Bottom Row — Left | Line Chart | ROIC and ROE trend over time |
| Bottom Row — Right | Scatter Chart | Revenue Growth vs ROIC (bubble size = last year revenue) |
6.6.2 Step-by-Step Visual Build
[Latest Revenue Growth] measure into the Fields well. Format: set the title to "Revenue Growth %", data label font size to 24pt. Repeat for Operating Margin, ROIC, and Net Margin — arrange them in a 2×2 grid. Use the Format pane to set each card's background to a subtle gradient and apply the conditional color measure to the data label via Format → Data Label → Color → fx (conditional formatting).ratio_data[Company] into the Field well. In Format → Slicer Settings → Style, select "Dropdown." Enable "Select All" and "Single Select" if desired. This slicer will filter every other visual on the page.DateTable[FiscalYear] to X-axis. Drag [Operating Margin], [Gross Margin], and [Net Margin] to the Y-axis well. In Format: set Y-axis range to 0–50%, add a horizontal reference line at 15% (typical WACC proxy), and title it "Margin Trends."ratio_data[Company] to Y-axis, [Latest ROIC] to X-axis. In Format → Data Colors, apply conditional formatting: green if ROIC > 15%, orange if 10–15%, red if <10%. Sort descending by ROIC. Add a reference line at 10% (WACC).DateTable[FiscalYear] to X-axis. Drag [ROIC] and [ROE] to Y-axis. Use distinct colors (blue for ROIC, orange for ROE). Add a horizontal reference line at 10%.[Revenue Growth] to X-axis, [ROIC] to Y-axis, ratio_data[Company] to Legend, and revenue to Size (you will need a separate measure for this). Add quadrant lines: vertical at 0% growth, horizontal at 10% ROIC.6.6.3 Cross-Highlighting and Interactivity
Power BI automatically enables cross-filtering and cross-highlighting between visuals. Verify these interactions:
- Click a bar in the ROIC bar chart → all other visuals filter to that company.
- Click a year on the trend chart → KPI cards stay at latest year (by design), but the trend chart highlights that point.
- Use the Company slicer → everything updates.
To customize interactions: select a visual → Format ribbon → Edit Interactions → choose which visuals it filters, highlights, or ignores.
Run these five clicks on your dashboard. Any "no" means something is mis-connected:
- Slicer → one company: do all six visuals update?
- Click one bar in the ROIC chart: do the KPI cards stay at latest year while trends re-filter?
- Click a middle year on a trend line: does the rest of the page respond?
- Ctrl-click two companies: do averages/maximums still make sense (remember the DAX Lab)?
- Clear all filters (top-right eraser icon): is the page back to the full view?
Fix any "no" before moving on — a dashboard where one chart ignores the slicer destroys the CFO-five-seconds promise of this whole session.
With your dashboard (all companies visible), find and jot down:
- The single strongest value creator on your Growth-vs-ROIC scatter — and the click-sequence you used to prove it
- One company whose margins and ROIC disagree (healthy margin, weak ROIC — or the reverse) — what does the decomposition tell you?
- One year where something broke (a dip, a spike) across multiple charts at once
Partner check: swap seats, reproduce each other's three findings by clicking — if you can't reproduce it, the insight (or the dashboard) isn't solid.
6.7 Executive Dashboard Design Principles
A technically correct dashboard can still fail if it does not communicate effectively. Here are the design principles that separate a professional executive dashboard from an amateur chart collection:
6.7.1 The Five Principles
| Principle | What It Means | How to Apply in Power BI |
|---|---|---|
| 1. Hierarchy | The most important information should occupy the most prominent visual real estate. | KPI cards go top-left (first thing the eye sees). Supporting charts go in the middle. Detail tables go at the bottom or on a separate page. |
| 2. Consistency | The same color should always mean the same thing. The same metric should always use the same format. | Define a color palette: green = good, red = bad, blue = primary metric. Use the JSON theme file to enforce it globally (File → Options → Preview Features → Customize current theme). |
| 3. Narrative | The dashboard should tell a story: Overview → Trend → Comparison → Detail. | Arrange visuals in reading order. Add text boxes as section headers. Use the "Z-pattern" layout: KPI overview (top-left) → company filter (top-right) → trend (middle-left) → comparison (middle-right) → detail (bottom). |
| 4. Restraint | Less is more. 6–8 well-chosen visuals communicate more than 20 cluttered ones. | Limit to one page of key visuals. Move supplementary detail to tooltips or a second page. Remove gridlines, excessive borders, and distracting background images. |
| 5. Context | Every number needs a benchmark. "15% ROIC" means nothing without knowing whether it is good or bad. | Add reference lines (WACC at 10%), conditional formatting (green/red), and small text annotations explaining what to look for. |
6.7.2 Color Palette for Financial Dashboards
| Purpose | Color | HEX Code |
|---|---|---|
| Primary metric (headline KPI) | Royal Blue | #6C8CFF |
| Positive / Above benchmark | Teal Green | #00C9A7 |
| Warning / Marginal | Amber | #F0A040 |
| Negative / Below benchmark | Red | #E0556A |
| Neutral / Background element | Muted Grey | #A0A0B8 |
| Dark background | Dark Navy | #1C1C2E |
Swap screens with your partner. Audit their dashboard against the five principles — one line each:
- Hierarchy: What did your eye see FIRST? Is that the most important message?
- Consistency: Does green/red mean the same thing on every visual? Any metric formatted two ways?
- Narrative: Read the page aloud top-left to bottom-right — does it tell one coherent story, or three unrelated charts?
- Restraint: Count the visuals and the ink. What ONE element would you delete for more impact?
- Context: Pick any number on their page — can you tell within 3 seconds if it's good or bad? If not, what benchmark is missing?
Deliver one compliment + one fix. Then swap back and apply the fix you received before the project submission. Excel track: run the identical audit on the PivotChart sheet layout.
6.8 Publishing and Sharing Your Dashboard
Once your dashboard is complete, you need to share it. Power BI offers multiple sharing paths depending on your audience:
| Method | Best For | How |
|---|---|---|
| Publish to Power BI Service | Sharing with faculty, classmates, or colleagues who have Power BI accounts | Home → Publish → Select a workspace → Share the link. Requires Power BI Pro or Microsoft 365 A1+ license. |
| Export to PDF | Including the dashboard in a report or submitting as an assignment | File → Export → Export to PDF. Captures the current filter state. |
| Export to PowerPoint | Presenting in a live meeting | File → Export → Export to PowerPoint → Embed live data (if published) or static images. |
| Screenshot | Quick sharing via WhatsApp, email, or embedding in documents | Use the Snipping Tool or Win+Shift+S. Capture the full dashboard at a readable resolution (1920×1080 recommended). |
| Share .pbix file | Collaborative editing — others can open your file in Power BI Desktop | Save the .pbix file and share via OneDrive, Teams, or USB. Recipients need Power BI Desktop installed. |
Which sharing method for each scenario? (Some have more than one defensible answer — justify.)
- Submitting this session's project to faculty, who will grade it without Power BI installed
- Live investment-committee meeting where the CFO may ask exactly one drill-down question
- A teammate must fix a broken DAX measure in your model while you are on leave
- Posting a one-image summary to the class WhatsApp group tonight
Pair: Compare choices. For scenario 2, what do you LOSE by exporting to PowerPoint instead of presenting live?
Check your answers
1) Export to PDF (or screenshots) — readable anywhere, fixed filter state. 2) Publish to Service + share link, or present Desktop live — the drill-down answer requires interactivity. 3) Share the .pbix — collaborative editing needs the source file. 4) Screenshot (Win+Shift+S) at 1920×1080. Scenario 2 PowerPoint loss: the five-second CFO answer — static slides are exactly the trap this session opened with.
6.9 Alternative: Google Looker Studio (Browser-Based, Free)
If you cannot run Power BI Desktop (macOS/Linux users), Google Looker Studio (formerly Data Studio) is a capable, free alternative that runs entirely in the browser. The dashboard design principles are identical; only the tool mechanics differ.
| Power BI Feature | Looker Studio Equivalent |
|---|---|
| Get Data (CSV import) | Create Data Source → File Upload → CSV |
| DAX Measures | Calculated Fields (similar formula language, less powerful) |
| Slicers | Filter Control (dropdown, single-select, multi-select) |
| KPI Cards | Scorecard visual |
| Line Chart | Time Series chart |
| Scatter Chart | Scatter chart (same name) |
| Cross-filtering | Automatic; configure via chart interactions |
| Publish/Share | Share → Get report link (view-only or edit access) |
The CSV files generated by the Python script in Section 6.3 are directly importable into Looker Studio. The workflow is: Create Report → Add Data (File Upload) → drag charts onto the canvas → configure dimensions and metrics.
Hands-On Project: Build and Present a Complete Financial Analysis Dashboard
Your task is to build a complete, boardroom-ready financial analysis dashboard for 5 Indian companies using Power BI (or Looker Studio). You will source the data, build the model, design the visuals, and produce a final dashboard that tells a clear financial story. This dashboard will be part of your capstone project deliverables.
Excel Option No Power BI or Looker access? Build the identical dashboard in Excel per Section 6.1's Excel Alt box: one sheet with 3–4 PivotCharts (ROIC comparison, margin trend, ROIC trend, KPI summary table), all connected to shared Company/Year slicers via Report Connections, arranged in the same Z-pattern and passing the same five-point design audit. The 5 screenshots and the 200-word insight summary are identical requirements regardless of tool.
This is where I need your help. Follow the steps below in your Power BI environment and share the outputs. I will incorporate your actual screenshots and results into this chapter.
Steps
- Choose your companies (5 companies, either within one sector or across sectors). For example:
- Single-sector: TCS, Infosys, Wipro, HCL Tech, Tech Mahindra (IT Services)
- Cross-sector: TCS, Asian Paints, Reliance, Sun Pharma, Titan
- Run the data generation script from Section 6.3.2. Update the COMPANIES dictionary with your chosen tickers. Verify the two CSV files are generated with correct data.
- Import into Power BI and build the data model (Section 6.4). Create the DateTable and relationships.
- Write DAX measures (Section 6.5) — at minimum: Latest Revenue Growth, Latest Operating Margin, Latest ROIC, Latest Net Margin, and the trend measures for line charts.
- Build the visuals (Section 6.6) — KPI cards, slicer, line charts, bar chart, and scatter plot. Apply conditional formatting.
- Apply design principles (Section 6.7) — dark theme, consistent colors, reference lines, proper labeling.
- Capture the following screenshots:
- Screenshot A: The data generation script output in your terminal (showing the CSV row counts)
- Screenshot B: Power BI Model view showing the tables and relationships
- Screenshot C: The full Report view with all visuals and all companies visible
- Screenshot D: The dashboard filtered to a single company (click on one company in the slicer)
- Screenshot E: The most interesting insight you discovered — e.g., a surprising ROIC comparison, a margin trend that tells a story, or the Growth vs ROIC scatter plot showing a clear value creator
- Write a 200-word insight summary: Based on your dashboard, which company has the strongest financial profile? Which is the weakest? What surprised you?
- Live Defence (NEW): In pairs, present your dashboard for 3 minutes — but your partner plays the CFO and must ask at least one "what if…" filter question during the presentation (e.g., "exclude the smallest company," "show only the last 3 years"). You must answer it live with slicer clicks, not words. Then swap. If you couldn't answer live, note what your dashboard was missing — and fix it.
Key Takeaways
A dashboard tells a story that raw data cannot. The same financial metrics that fill pages of tables become instantly comprehensible when arranged in a well-designed Power BI dashboard with interactivity.
70% of dashboard effort is data preparation. The Python pipeline you built in Chapters 3–5 generates the clean, structured data that makes Power BI import seamless. Never skip this step.
DAX measures are reusable across visuals. Write a measure once — like [Latest ROIC] — and use it in KPI cards, bar charts, tables, and conditional formatting rules. Changes propagate everywhere.
Follow the five design principles: Hierarchy, Consistency, Narrative, Restraint, and Context. A technically correct dashboard that violates these principles fails to communicate.
Interactive dashboards are the capstone deliverable for this course. Your ability to present financial analysis through Power BI is a direct job skill for investment banking, equity research, and corporate finance roles.
Test Your Understanding
1. In Power BI, what is the correct order of the four-step workflow for building a dashboard?
2. Why is a "long format" (tall) data structure preferred over a "wide format" in Power BI?
3. What does a DAX measure do that a regular calculated column does NOT?
4. In the Z-pattern dashboard layout, where should the most important KPI metrics be placed?
5. You want a KPI card to display the latest year's ROIC for whichever company is selected in the slicer. Which DAX pattern correctly achieves this?
Look at the dashboard you built today. Which single visual would you show FIRST in a boardroom, and which one would you cut if you had only 30 seconds? Write both down — this "elevator view" instinct is what you will refine into the executive capstone dashboard in Session 24.