Home / Power BI / Visualization Design and Custom Visuals
Power BI

Visualization Design and Custom Visuals

Master Power BI visualization design: comprehensive chart selection guidelines, cognitive load reduction strategies, custom visual development with TypeScrip...

What you will learn

Practical execution with concise explanations, real implementation patterns, and production-ready recommendations.

], "background": "#FFFFFF", "foreground": "#000000", "tableAccent": "#0078D4", "bad": "#D83B01", "neutral": "#FFB900", "good": "#107C10", "minimum": "#F2F2F2", "center": "#FFB900", "maximum": "#107C10" }


### Typography Hierarchy

```text
Report Design Typography Standards:

Page Title:          Segoe UI, 18-20pt, Bold
Section Headers:     Segoe UI, 14-16pt, Semibold
Visual Titles:       Segoe UI, 12pt, Semibold
Axis Labels:         Segoe UI, 10pt, Regular
Data Labels:         Segoe UI, 9-10pt, Regular
Footnotes:           Segoe UI, 8pt, Italic

Line Height: 1.4-1.6 (optimal readability)
Avoid: >3 font sizes on one page

Layout Best Practices

Diagram: See the official Microsoft documentation for architecture details.

Accessibility Compliance (WCAG 2.1)

Color Contrast Requirements

# PowerShell script to validate color contrast ratios

function Test-ColorContrast {

> **Architecture Overview:** param(


## Alternative Text Configuration


![Alternative Text Configuration](/images/articles/power-bi/2025-04-14-visualization-design-custom-visuals-ctx-1.svg)

*Figure: Configuration editor – appsettings sections with environment overrides.*


> **Architecture Overview:** How to Add Alt Text to Power BI Visuals:


### Color-Blind Friendly Palettes

```json
// Tested for Deuteranopia, Protanopia, Tritanopia

{
  "name": "AccessiblePalette",
  "dataColors": [
```python
"#0173B2",  // Blue - Safe for all
"#DE8F05",  // Orange - Safe for all
"#029E73",  // Green - Distinct from blue/orange
"#CC78BC",  // Purple - Additional option
"#CA9161",  // Brown - Earth tone alternative
"#949494",  // Gray - Neutral option
"#ECE133"   // Yellow - Use sparingly (low contrast)```
  ]
}

// Color-blind simulator tools:
// - https://www.color-blindness.com/coblis-color-blindness-simulator/
// - Chrome extension: "Colorblinding"

Custom Visual Development

Setup Development Environment

## Install Power BI Custom Visuals Tools





## 1. Install Node.js (LTS version 14+)




## Download from: https://nodejs.org/





## 2. Install pbiviz CLI globally
npm install -g powerbi-visuals-tools





## 3. Create SSL certificate for dev testing
pbiviz --install-cert





## 4. Verify installation
pbiviz --version




## Should show: PowerBI Custom Visual Tool 4.x.x

Write-Host "✅ Power BI Visuals SDK installed successfully" -ForegroundColor Green





Expected output:

added 245 packages in 8s
found 0 vulnerabilities

Terminal output for npm install

Create Custom Visual Project

Diagram: See the official Microsoft documentation for architecture details.

Custom Visual Example: Advanced KPI Card

Custom Visual Example: Advanced KPI Card

Figure: Power BI Desktop – report canvas with visuals, fields, and format pane.

// src/visual.ts - Custom KPI Visual





module powerbi.extensibility.visual {
```python
import DataView = powerbi.DataView;
import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions;
import IVisual = powerbi.extensibility.visual.IVisual;
import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions;

export class AdvancedKPI implements IVisual {
    private target: HTMLElement;
    private container: HTMLDivElement;
    private settings: VisualSettings;

    constructor(options: VisualConstructorOptions) {
        this.target = options.element;
        
        // Create container
        this.container = document.createElement("div");
        this.container.className = "kpi-container";
        this.target.appendChild(this.container);
    }

    public update(options: VisualUpdateOptions) {
        // Get data view
        const dataView: DataView = options.dataViews[0];
        
        if (!dataView || !dataView.categorical) {
            return;
        }

        // Extract values
        const category = dataView.categorical.categories[0];
        const values = dataView.categorical.values[0];
        
        const actualValue = values.values[0] as number;
        const targetValue = dataView.categorical.values[1]?.values[0] as number || 0;
        
        // Calculate percentage
        const percentage = targetValue > 0 
            ? ((actualValue / targetValue) * 100) 
            : 0;
        
        const percentageText = percentage >= 100 
            ? `+${(percentage - 100).toFixed(1)}%` 
            : `-${(100 - percentage).toFixed(1)}%`;
        
        const statusClass = percentage >= 100 ? "positive" : "negative";
        
        // Render HTML
        this.container.innerHTML = `
            <div class="kpi-card ${statusClass}">
                <div class="kpi-label">${category.source.displayName}</div>
                <div class="kpi-value">${this.formatNumber(actualValue)}</div>
                <div class="kpi-target">
                    Target: ${this.formatNumber(targetValue)}
                </div>
                <div class="kpi-progress">
                    <div class="progress-bar" style="width: ${Math.min(percentage, 100)}%"></div>
                </div>
                <div class="kpi-percentage ${statusClass}">${percentageText}</div>
            </div>
        `;
    }

    private formatNumber(value: number): string {
        if (value >= 1000000) {
            return `$${(value / 1000000).toFixed(1)}M`;
        } else if (value >= 1000) {
            return `$${(value / 1000).toFixed(0)}K`;
        } else {
            return `$${value.toFixed(0)}`;
        }
    }
}```
}

Custom Visual Styling

// style/visual.less

.kpi-container {
```yaml
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-family: "Segoe UI", Arial, sans-serif;```
}

.kpi-card {
```yaml
background: #ffffff;
border-radius: 8px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
text-align: center;
min-width: 200px;

&.positive {
    border-left: 4px solid #107C10;
}

&.negative {
    border-left: 4px solid #D83B01;
}```
}

.kpi-label {
```yaml
font-size: 12px;
color: #666;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;```
}

.kpi-value {
```yaml
font-size: 36px;
font-weight: 600;
color: #000;
margin-bottom: 4px;```
}

.kpi-target {
```yaml
font-size: 11px;
color: #999;
margin-bottom: 12px;```
}

.kpi-progress {
```yaml
width: 100%;
height: 4px;
background: #f0f0f0;
border-radius: 2px;
overflow: hidden;
margin-bottom: 12px;

.progress-bar {
    height: 100%;
    background: linear-gradient(90deg, #107C10, #00B294);
    transition: width 0.3s ease;
}```
}

.kpi-percentage {
```yaml
font-size: 14px;
font-weight: 600;

&.positive {
    color: #107C10;
}

&.negative {
    color: #D83B01;
}```
}

Capabilities Configuration

// capabilities.json - Defines data roles and properties

{
```text
"dataRoles": [
    {
        "displayName": "Category",
        "name": "category",
        "kind": "Grouping"
    },
    {
        "displayName": "Actual Value",
        "name": "actualValue",
        "kind": "Measure"
    },
    {
        "displayName": "Target Value",
        "name": "targetValue",
        "kind": "Measure"
    }
],
"dataViewMappings": [
    {
        "categorical": {
            "categories": {
                "for": { "in": "category" },
                "dataReductionAlgorithm": { "top": { "count": 1 } }
            },
            "values": {
                "select": [
                    { "bind": { "to": "actualValue" } },
                    { "bind": { "to": "targetValue" } }
                ]
            }
        }
    }
],
"objects": {
    "kpiCard": {
        "displayName": "KPI Card Settings",
        "properties": {
            "showTarget": {
                "displayName": "Show Target",
                "type": { "bool": true }
            },
            "targetColor": {
                "displayName": "Target Color",
                "type": { "fill": { "solid": { "color": true } } }
            }
        }
    }
}```
}

Testing and Packaging

Architecture Overview: ## Development workflow

Theme Customization and Branding

Complete Theme JSON Example

{
  "name": "CorporateTheme",
  "dataColors": [
```text
"#0078D4", "#50E6FF", "#FFB900", "#00B294", 
"#E74856", "#00CC6A", "#FF8C00"```
  ],
  "background": "#FFFFFF",
  "foreground": "#000000",
  "tableAccent": "#0078D4",
  
  "visualStyles": {
```text
"*": {
  "*": {
    "title": [{
      "fontSize": 12,
      "fontFamily": "Segoe UI",
      "bold": true,
      "color": {"solid": {"color": "#000000"}}
    }],
    "background": [{
      "color": {"solid": {"color": "#FFFFFF"}},
      "transparency": 0
    }],
    "border": [{
      "color": {"solid": {"color": "#E0E0E0"}},
      "show": true
    }]
  }
},
"card": {
  "*": {
    "categoryLabels": [{
      "fontSize": 12,
      "color": {"solid": {"color": "#666666"}}
    }],
    "dataLabels": [{
      "fontSize": 24,
      "fontFamily": "Segoe UI Semibold",
      "color": {"solid": {"color": "#000000"}}
    }]
  }
},
"lineChart": {
  "*": {
    "dataPoint": [{
      "showAllDataPoints": true
    }],
    "legend": [{
      "show": true,
      "position": "Top",
      "fontSize": 10
    }]
  }
}```
  }
}

Apply Theme Programmatically

## Apply theme to all reports in workspace

$theme = Get-Content "CorporateTheme.json" -Raw
$workspaceId = "workspace-guid"





## Using Power BI REST API
$headers = @{
```text
"Authorization" = "Bearer $accessToken"
"Content-Type" = "application/json"```
}





$reports = Invoke-RestMethod -Uri "https://api.powerbi.com/v1.0/myorg/groups/$workspaceId/reports" -Headers $headers

foreach ($report in $reports.value) {
```text
Write-Host "Applying theme to: $($report.name)" -ForegroundColor Cyan

$uri = "https://api.powerbi.com/v1.0/myorg/groups/$workspaceId/reports/$($report.id)/ApplyTheme"

$body = @{
    theme = $theme | ConvertFrom-Json
} | ConvertTo-Json -Depth 10

Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body

Write-Host "✅ Theme applied successfully" -ForegroundColor Green```
}

Performance Optimization

Visual Performance Checklist

Architecture Overview: ☑ Data Volume:

Performance Testing Script

## Analyze report performance

function Test-PowerBIReportPerformance {
```powershell
param(
    [string]$ReportId,
    [string]$WorkspaceId
)





Write-Host "Analyzing report performance..." -ForegroundColor Cyan

## Get report pages
$uri = "https://api.powerbi.com/v1.0/myorg/groups/$WorkspaceId/reports/$ReportId/pages"
$pages = Invoke-RestMethod -Uri $uri -Headers $headers





foreach ($page in $pages.value) {
    Write-Host "`nPage: $($page.displayName)" -ForegroundColor Yellow
    
    $visualCount = $page.visuals.Count
    Write-Host "  Visuals: $visualCount"
    
    if ($visualCount -gt 12) {
        Write-Host "  ⚠️ WARNING: Too many visuals (>12)" -ForegroundColor Red
        Write-Host "  Recommendation: Split into multiple pages or use drill-through"
    } elseif ($visualCount -gt 8) {
        Write-Host "  ⚠️ CAUTION: High visual count (8-12)" -ForegroundColor Yellow
    } else {
        Write-Host "  ✅ Visual count optimal (<8)" -ForegroundColor Green
    }
    
    # Check for heavy visuals
    $matrixVisuals = ($page.visuals | Where-Object { $_.type -eq "matrix" }).Count
    $tableVisuals = ($page.visuals | Where-Object { $_.type -eq "table" }).Count
    
    if ($matrixVisuals + $tableVisuals -gt 2) {
        Write-Host "  ⚠️ Multiple table/matrix visuals detected - consider consolidation" -ForegroundColor Yellow
    }
}```
}

Best Practices Summary

Best Practices Summary

Figure: Configuration and management dashboard with status overview.

☑ Design Principles:
  ☐ Clarity over complexity - every visual answers a question
  ☐ Consistency across reports (colors, layout, naming)
  ☐ F-pattern layout (top-left → top-right → bottom)
  ☐ White space = cognitive breathing room
  ☐ 8-point grid alignment





☑ Chart Selection:
  ☐ Match visual type to data story
  ☐ Avoid pie charts with >5 slices
  ☐ No 3D effects ever
  ☐ Use sorted bar charts for rankings
  ☐ Line charts for trends over time

☑ Color Strategy:
  ☐ Max 7 colors in palette
  ☐ Use color-blind friendly palettes
  ☐ Maintain 4.5:1 contrast ratio (WCAG AA)
  ☐ Consistent color coding (e.g., red = negative)
  ☐ Enforce theme JSON usage

☑ Accessibility:
  ☐ Alt text for all visuals
  ☐ Keyboard navigation support
  ☐ Color not sole indicator
  ☐ Font size ≥10pt
  ☐ Test with screen readers

☑ Performance:
  ☐ 8-12 visuals max per page
  ☐ Use measures over calculated columns
  ☐ Limit data rows returned
  ☐ Optimize custom visual rendering
  ☐ Disable cross-highlight on busy pages

☑ Custom Visuals:
  ☐ Use AppSource visuals when possible
  ☐ Code review before production deployment
  ☐ Version control visual code
  ☐ Document data role requirements
  ☐ Test across browsers and devices

☑ Governance:
  ☐ Central theme library
  ☐ Approved custom visual list
  ☐ Design review process
  ☐ Performance benchmarks
  ☐ Accessibility audit quarterly

Troubleshooting Guide

Issue 1: Visuals Load Slowly

Diagnosis:

  • Too many visuals on page
  • High cardinality data
  • Complex DAX calculations
  • Inefficient data model

Resolution:

## Analyze query performance in Power BI Desktop

1. Go to: View → Performance Analyzer
2. Click "Start Recording"
3. Refresh visuals
4. Review timing breakdown:
   - DAX query time
   - Visual display time
   - Other





## Focus optimization on slowest queries




## Typical issues:




## - Missing relationships




## - Calculated columns instead of measures




## - No aggregations defined

Issue 2: Custom Visual Blocked by Admin

Diagnosis:

Error: "This visual is not approved for your organization"


> **Architecture Overview:** **Resolution:**

// Create brand-specific theme JSON

{
  "name": "BrandTheme",
  "dataColors": [
```text
"#<BRAND_PRIMARY>",
"#<BRAND_SECONDARY>",
"#<BRAND_ACCENT_1>",
"#<BRAND_ACCENT_2>"```
  ],
  "background": "#<BACKGROUND_COLOR>",
  "foreground": "#<TEXT_COLOR>",
  "visualStyles": {
```text
"*": {
  "*": {
    "title": [{
      "fontFamily": "<BRAND_FONT>",
      "color": {"solid": {"color": "#<BRAND_PRIMARY>"}}
    }]
  }
}```
  }
}

// Apply via: View → Themes → Browse for themes

Visual Types Deep Dive: When and Why

Core Chart Types

Bar and Column Charts (Comparison)

Use When:
✓ Comparing values across categories (5-20 categories optimal)
✓ Ranking (Top N products, Bottom performers)
✓ Time series (column) or categorical comparison (bar)

Best Practices:
- Start Y-axis at zero for honest comparison
- Sort by value unless time series
- Use horizontal bars for long category names
- Limit to 20 bars maximum (consider TOP N filter)

Avoid:
✗ 3D effects (distort perception)
✗ Multiple stacked series (hard to compare)
✗ Non-zero Y-axis start (misleading)

Line Charts (Trends Over Time)

Use When:
✓ Showing continuous data trends
✓ Multiple series comparison over time
✓ Highlighting change patterns

Best Practices:
- Maximum 5 lines per chart (readability)
- Use consistent time intervals
- Add markers at data points if sparse data
- Different line styles (solid, dashed) for accessibility

Avoid:
✗ Categorical data on X-axis
✗ Too many series (spaghetti chart)
✗ Inconsistent time periods

Pie and Donut Charts (Part-to-Whole)

Use When:
✓ Showing simple proportions (2-5 segments maximum)
✓ One series only
✓ Percentages add to 100%

Best Practices:
- Start at 12 o'clock, arrange clockwise by size
- Label with percentages and values
- Limit to 5 slices (use "Other" category)
- Avoid 3D (distorts perception)

Better Alternative:
→ 100% Stacked Bar Chart (easier comparison)

Avoid:
✗ More than 5 slices
✗ Similar-sized slices (hard to distinguish)
✗ Multiple pies for comparison

Scatter Charts (Correlation and Outliers)

Use When:
✓ Exploring relationships between variables
✓ Identifying clusters and outliers
✓ Three dimensions with bubble size

Best Practices:
- Add trend line if correlation expected
- Use color for third dimension grouping
- Bubble size for fourth dimension (limit variation)
- Include quadrant lines for strategic analysis

Power BI Enhancement:
- Enable Play Axis for time animation
- Add constant lines for targets/benchmarks
- Use zoom slider for detailed exploration

Cards and KPIs (Single Value Focus)

Use When:
✓ Highlighting single most important metric
✓ Dashboard summary at top
✓ Comparison to goal (KPI visual)

Best Practices:
- Position top-left for maximum visibility
- Use large, readable fonts (40-60pt)
- Add sparkline for context (trend)
- Green/red indicators for good/bad performance

KPI Visual Specifics:
- Set goal value (target)
- Define direction (higher/lower is better)
- Color coding: Green (good), Red (bad), Yellow (warning)

Tables and Matrices (Detailed Data)

Use When:
✓ Users need precise values
✓ Multi-dimensional analysis (matrix)
✓ Export to Excel required

Best Practices:
- Limit to 10-15 rows visible (use filters)
- Add conditional formatting (data bars, color scales)
- Right-align numbers, left-align text
- Freeze headers for scrolling

Matrix Specifics:
- Expand/collapse functionality for hierarchies
- Subtotals and grand totals clearly labeled
- Drill-down enabled for detailed exploration

Maps (Spatial Distribution)

Types:
- Map: Geographic data visualization
- Filled Map: Choropleth (regions colored by value)
- ArcGIS Maps: Advanced spatial analysis
- Shape Map: Custom map boundaries

Use When:
✓ Geographic dimension is key insight
✓ Spatial patterns matter (clusters, hotspots)
✓ Store/location analysis

Best Practices:
- Use bubble size or color saturation for values
- Add tooltips with detailed metrics
- Enable zoom and pan for detailed exploration
- Consider accessibility (color alone insufficient)

Performance Note:
- Limit data points (<10,000 for responsive maps)
- Use aggregated data (state/region vs individual addresses)

Advanced Visualizations

Decomposition Tree (Root Cause Analysis)

Use Case: Drilling into metric drivers

Example:
Total Sales → Region → Product Category → Customer Segment

User Action:
Click "+" to expand next level based on AI suggestion or manual selection

Benefits:
✓ Interactive exploration without predefined hierarchy
✓ AI-powered insights (highest/lowest contributors)
✓ Multiple path exploration

Key Influencers (Driver Analysis)

Use Case: "Why did metric increase/decrease?"

Setup:
- Analyze: [Metric to explain]
- Explain By: [Potential factors]

Output:
- Top factors driving metric change
- Ranked by influence strength
- Segment analysis
- Statistical significance indicators

Requirements:
- Minimum 10 data points per category
- Numeric or categorical explanation variables

Q&A Visual (Natural Language)

User Experience:
"Show sales by region for last quarter"
→ Auto-generates appropriate visual

Benefits:
✓ Non-technical user accessibility
✓ Ad-hoc analysis without training
✓ Synonym support (Revenue = Sales)

Setup Requirements:
- Define synonyms (Model → Q&A Setup)
- Add suggested questions
- Review and approve terms
- Test common user queries

Paginated Report Visual (Pixel-Perfect)

Use Case: Regulatory reports, invoices, statements

Embedded Power BI Report Builder report within dashboard

Benefits:
✓ Precise formatting control
✓ Multi-page reports
✓ Print-optimized
✓ Subscriptions for distribution

Considerations:
- Requires Premium capacity
- Different authoring tool (Report Builder)
- Static layout (not responsive)

Visualization Anti-Patterns to Avoid

❌ Common Mistakes

1. Chart Junk (Unnecessary Elements)

Remove:
- 3D effects (distort perception)
- Heavy gridlines (visual clutter)
- Excessive borders/shadows
- Decorative icons
- Background images

Result: Faster comprehension, professional appearance

2. Dual-Axis with Different Scales

Problem:
Left axis: Sales (0-100K)
Right axis: Units (0-500)
→ Misleading correlation

Solution:
- Use separate charts
- OR: Normalize both to percentages (0-100%)
- OR: Use scatter plot (both on same axis)

3. Truncated Y-Axis

Problem:
Y-axis starts at 50,000 instead of 0
→ Exaggerates small differences

Solution:
- Always start at zero for bar/column charts
- Exception: Line charts can truncate if trend is focus

4. Too Many Visuals Per Page

Problem:
15+ visuals on single page
→ Slow load, cognitive overload

Solution:
- Max 12 visuals per page
- Use drill-through for details
- Create multiple pages by audience
- Bookmarks for different views

5. Rainbow Color Palettes

Problem:
Using 10+ colors
→ No color conveys meaning, inaccessible

Solution:
- 3-5 color maximum for categorical
- Sequential palette for continuous data
- Diverging palette for positive/negative
- Reserve red/green for good/bad only

Architecture Decision and Tradeoffs

When designing business intelligence solutions with Power BI, consider these key architectural trade-offs:

Approach Best For Tradeoff
Managed / platform service Rapid delivery, reduced ops burden Less customisation, potential vendor lock-in
Custom / self-hosted Full control, advanced tuning Higher operational overhead and cost

Recommendation: Start with the managed approach for most workloads and move to custom only when specific requirements demand it.

Validation and Versioning

  • Last validated: April 2026
  • Validate examples against your tenant, region, and SKU constraints before production rollout.
  • Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.

Security and Governance Considerations

  • Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
  • Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
  • Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.

Cost and Performance Notes

  • Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
  • Baseline performance with synthetic and real-user checks before and after major changes.
  • Scale resources with measured thresholds and revisit sizing after usage pattern changes.

Official Microsoft References

  • https://learn.microsoft.com/power-bi/
  • https://learn.microsoft.com/power-bi/guidance/
  • https://learn.microsoft.com/fabric/

Public Examples from Official Sources

  • These examples are sourced from official public Microsoft documentation and sample repositories.
  • Documentation examples: https://learn.microsoft.com/power-bi/
  • Sample repositories: https://github.com/microsoft/PowerBI-Developer-Samples
  • Prefer adapting these examples to your tenant, subscriptions, and governance requirements before production use.

Key Takeaways

  • Chart selection is driven by data story (comparison, composition, distribution, relationship, spatial)
  • Cognitive load reduction uses limited color palettes, typography hierarchy, and strategic white space
  • Accessibility is non-negotiable—WCAG 2.1 Level AA minimum for enterprise reports
  • Custom visuals require TypeScript knowledge, pbiviz CLI, and testing across scenarios
  • Theme JSON enforces consistent branding across all reports programmatically
  • Performance degrades rapidly above 12 visuals per page—use drill-through and bookmarks
  • Layout patterns follow F-pattern reading (top-left most important)
  • Color contrast must meet 4.5:1 ratio for normal text, 3:1 for large text (18pt+)
  • Alternative text should be descriptive, not decorative
  • Governance includes approved visual lists, central theme library, and design review process

Next Steps

  1. Audit existing reports using performance analyzer
  2. Create brand theme JSON with approved colors and fonts
  3. Implement accessibility alt text and contrast compliance
  4. Set up custom visual development environment if needed
  5. Establish design system documentation with examples
  6. Create visual selection guide for report authors
  7. Define performance benchmarks (<3 seconds load time target)
  8. Quarterly accessibility audits with assistive technology testing
  9. User testing sessions with target audience
  10. Iterate based on feedback and usage analytics

Additional Resources


Design with purpose. Build with precision. Deliver with impact.

Discussion