Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
data-goblin avatar

Python Visuals

  • 32 installs
  • 836 repo stars
  • Updated July 29, 2026
  • data-goblin/power-bi-agentic-development

Create Python visuals in Power BI PBIR reports using matplotlib and seaborn scripts injected into the report.

About

Guidance for creating Python visuals in PBIR reports with matplotlib and seaborn patterns. A developer uses it to add a Python-scripted chart to a Power BI report via the pbir CLI or direct JSON editing.

  • Creates pythonVisual charts with matplotlib/seaborn
  • Applies via pbir CLI or direct PBIR JSON edits

Python Visuals by the numbers

  • 32 all-time installs (skills.sh)
  • Ranked #1,096 of 2,064 Data Science & ML skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/data-goblin/power-bi-agentic-development --skill python-visuals

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs32
repo stars836
Last updatedJuly 29, 2026
Repositorydata-goblin/power-bi-agentic-development

What it does

Create Python visuals in Power BI PBIR reports using matplotlib and seaborn scripts injected into the report.

Files

SKILL.mdMarkdownGitHub ↗

Python Visuals in Power BI (PBIR)

Report modification requires tooling. Two paths exist:
1. `pbir` CLI (preferred) -- use the pbir command and the pbir-cli skill. Install with uv tool install pbir-cli or pip install pbir-cli. Check availability with pbir --version.
2. Direct JSON modification -- if pbir is not available, use the pbir-format skill (pbip plugin) for PBIR JSON structure and patterns. Validate every change with jq empty <file.json>.

>

If neither the pbir-cli skill nor the pbir-format skill is loaded, ask the user to install the appropriate plugin before proceeding with report modifications.

Python visuals execute matplotlib/seaborn scripts to render static PNG images on the Power BI canvas. Prefer seaborn over raw matplotlib for cleaner syntax and better defaults -- it handles most chart types with less code.

Visual Identity

  • visualType: pythonVisual
  • Data role: Values (columns and measures, multiple allowed)
  • Data variable: dataset (pandas DataFrame, auto-injected)
  • Row limit: 150,000 rows
  • Output: Static PNG at 72 DPI -- no interactivity

Workflow: Creating a Python Visual

Step 1: Add the Visual

Create the visual.json file manually (see pbir-format skill in the pbip plugin for JSON structure) with visualType: pythonVisual, field bindings for the columns and measures you need (use Values:Table.Column or Values:Table.Measure format), and position/size as required.

Step 2: Write the Script

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 4))
ax.bar(dataset["Date"], dataset["Sales"], color="#5B8DBE")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
plt.tight_layout()
plt.show()  # MANDATORY

Critical rules:

  • plt.show() is mandatory as the final line -- nothing renders without it
  • dataset is auto-injected as a pandas DataFrame; do not create it
  • Column names match the nativeQueryRef (display name) from field bindings
  • Only the last plt.show() call renders; multiple figures not supported

Step 2b: Review

Before presenting the script to the user, dispatch the python-reviewer agent to validate correctness and provide design feedback.

Step 3: Inject the Script

Set the script content in the visual's objects.script[0].properties.source literal value (see PBIR Format section below).

Escaping rules for visual.json injection:

The script must be encoded as a single-quoted DAX literal string inside expr.Literal.Value:

  • Newlines in the script become \n in the JSON string
  • Double quotes inside the script (e.g., "#5B8DBE") become \" in the JSON string
  • The entire script is wrapped in single quotes: 'import matplotlib...\nplt.show()'
  • See examples/visual/ for a complete real-world visual.json showing this encoding

Step 4: Validate

Validate JSON syntax with jq empty <visual.json> and inspect the visual.json to confirm script content and field bindings.

PBIR Format

Scripts are stored in visual.objects.script[0].properties:

{
  "source": {"expr": {"Literal": {"Value": "'import matplotlib.pyplot as plt\\n...\\nplt.show()'"}}},
  "provider": {"expr": {"Literal": {"Value": "'Python'"}}}
}

The CLI handles all escaping automatically.

Supported Libraries

Power BI Service (Python 3.11)

PackageVersionPurpose
matplotlib3.8.4Primary plotting
seaborn0.13.2Statistical visualization
numpy2.0.0Numerical computing
pandas2.2.2Data manipulation
scipy1.13.1Scientific computing
scikit-learn1.5.0Machine learning
statsmodels0.14.2Statistical models
pillow10.4.0Image processing

Not supported: plotly, bokeh, altair (networking blocked in Service).

Full package list: https://learn.microsoft.com/power-bi/connect-data/service-python-packages-support

Desktop

Any locally installed package works without restriction.

Best Practices

1. Always call `plt.show()` -- mandatory, must be the final line 2. Use `figsize=(w, h)` to match container aspect ratio (72 DPI output) 3. Remove chart chrome -- ax.spines["top"].set_visible(False) etc. 4. Use hex colors matching the report theme 5. Keep scripts simple -- 5-min timeout Desktop, 1-min Service 6. Minimize transforms -- do heavy computation in DAX/Power Query instead 7. Use `try/except` for robustness in production scripts 8. Copy data first -- data = dataset.copy() before manipulation

Limitations

ConstraintDesktopService
OutputStatic PNG, 72 DPIStatic PNG, 72 DPI
Timeout5 minutes1 minute
Row limit150,000150,000
Payload--30 MB
NetworkingUnrestrictedBlocked
GatewayPersonal onlyPersonal only
Cross-filter FROMNot supportedNot supported
Receive cross-filterYesYes
Publish to webNot supportedNot supported
Embed (app-owns-data)Not supportedNot supported

Script Structure Template

import matplotlib.pyplot as plt
import numpy as np

# 1. Guard against empty data
if dataset.empty:
    fig, ax = plt.subplots(1, 1, figsize=(6, 4))
    ax.text(0.5, 0.5, "No data available", ha='center', va='center', fontsize=14, color='#888888')
    ax.axis('off')
    plt.show()
else:
    # 2. Data preparation (dataset is auto-injected)
    data = dataset.copy()

    # 3. Create figure with explicit size
    fig, ax = plt.subplots(figsize=(8, 4))

    # 4. Plot
    ax.plot(data["X"], data["Y"], color="#5B8DBE", linewidth=2)

    # 5. Style
    ax.spines["top"].set_visible(False)
    ax.spines["right"].set_visible(False)
    ax.grid(axis="y", alpha=0.3)

    # 6. Layout and render
    plt.tight_layout()
    plt.show()

When to Use a Script Visual

Reach for a Python visual only when all of the following hold:

  • The chart has no native equivalent and no reasonable Deneb spec
  • The value is in a statistical computation that must run at render time (model fit, kernel density, forecast band), not just a shape Vega could draw
  • The visual does not need to be a cross-filter source, hover tooltips, publish-to-web, or app-owns-data embed
  • The report is served in a Pro/PPU or higher capacity with a Fabric-enabled region

If interactivity or cross-filtering matters, use Deneb (a static PNG cannot be a selection source). If the need is a small inline mark (sparkline, bar, status pill), use an SVG measure (no row cap, no timeout, no licensing/region gate, renders under publish-to-web). The script visual's niche is narrow: compute-at-render statistical plots for internal or org consumption.

Python vs R once a script visual is the right call: use Python when the computation leans on scikit-learn, statsmodels, or scipy, or when surrounding report logic is already Python. Use R for publication-quality statistical defaults and packages with no Python peer (forecast, corrplot, pheatmap, ridgeline/violin). Where equal, default to whichever language the report's other scripts use; mixing doubles the publish-time package surface to validate.

Do not default to a script visual because a chart type "looks statistical." A box plot, lollipop, or dumbbell is an SVG-measure or Deneb job; reserve scripts for charts that genuinely compute.

References

  • `references/data-model.md` -- dataset grouping mechanic, the row/byte caps, and how to force per-row input
  • `references/community-examples.md` -- seaborn gallery examples organized by chart type, plus matplotlib and Python Graph Gallery links
  • `references/chart-patterns.md` -- Common matplotlib/seaborn chart patterns (bar, heatmap, donut, KPI, area)
  • `examples/script/` -- Standalone Python scripts (bar-chart, trend-line) -- ready to inject into visual.json after escaping
  • `examples/visual/bar-chart.json` -- PBIR visual.json: horizontal stacked bar with PY comparison lines and % change labels
  • `examples/visual/kpi-card.json` -- PBIR visual.json: text-based KPI with value, % change indicator, and PY comparison
  • `examples/visual/trend-line.json` -- PBIR visual.json: area chart with line plot and monthly x-axis

Fetching Docs

To retrieve current Python visual / package support docs, use microsoft_docs_search + microsoft_docs_fetch (MCP) if available, otherwise mslearn search + mslearn fetch (CLI). Search based on the user's request and run multiple searches as needed to ensure sufficient context before proceeding.

Related Skills

  • `pbi-report-design` -- Layout and design best practices
  • `r-visuals` -- R Script visuals (same concept, different language)
  • `deneb-visuals` -- Vega/Vega-Lite visuals (interactive, vector-based alternative)
  • `svg-visuals` -- SVG via DAX measures (lightweight inline graphics)
  • `pbir-format` (pbip plugin) -- PBIR JSON format reference

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.