Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Python visual creation and matplotlib/seaborn patterns for PBIR reports. Automatically invoke when the user mentions "Python visual", "matplotlib in Power BI", "seaborn in Power BI", "pythonVisual", or asks to "create a Python visual", "add a matplotlib chart", "write a Python visual script".
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 142% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 103% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 132% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 75% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 97% | 0% |
> 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.
pythonVisualValues (columns and measures, multiple allowed)dataset (pandas DataFrame, auto-injected)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.
pythonimport 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 itdataset is auto-injected as a pandas DataFrame; do not create itnativeQueryRef (display name) from field bindingsplt.show() call renders; multiple figures not supportedBefore presenting the script to the user, dispatch the python-reviewer agent to validate correctness and provide design feedback.
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:
\n in the JSON string"#5B8DBE") become \" in the JSON string'import matplotlib...\nplt.show()'examples/visual/ for a complete real-world visual.json showing this encodingValidate JSON syntax with jq empty <visual.json> and inspect the visual.json to confirm script content and field bindings.
Scripts are stored in visual.objects.script[0].properties:
json{ "source": {"expr": {"Literal": {"Value": "'import matplotlib.pyplot as plt\\n...\\nplt.show()'"}}}, "provider": {"expr": {"Literal": {"Value": "'Python'"}}} }
The CLI handles all escaping automatically.
| Package | Version | Purpose | |---------|---------|---------| | matplotlib | 3.8.4 | Primary plotting | | seaborn | 0.13.2 | Statistical visualization | | numpy | 2.0.0 | Numerical computing | | pandas | 2.2.2 | Data manipulation | | scipy | 1.13.1 | Scientific computing | | scikit-learn | 1.5.0 | Machine learning | | statsmodels | 0.14.2 | Statistical models | | pillow | 10.4.0 | Image 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
Any locally installed package works without restriction.
plt.show() -- mandatory, must be the final linefigsize=(w, h) to match container aspect ratio (72 DPI output)ax.spines["top"].set_visible(False) etc.try/except for robustness in production scriptsdata = dataset.copy() before manipulation| Constraint | Desktop | Service | |------------|---------|---------| | Output | Static PNG, 72 DPI | Static PNG, 72 DPI | | Timeout | 5 minutes | 1 minute | | Row limit | 150,000 | 150,000 | | Payload | -- | 30 MB | | Networking | Unrestricted | Blocked | | Gateway | Personal only | Personal only | | Cross-filter FROM | Not supported | Not supported | | Receive cross-filter | Yes | Yes | | Publish to web | Not supported | Not supported | | Embed (app-owns-data) | Not supported | Not supported |
pythonimport 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()
Reach for a Python visual only when all of the following hold:
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/data-model.md -- dataset grouping mechanic, the row/byte caps, and how to force per-row inputreferences/community-examples.md -- seaborn gallery examples organized by chart type, plus matplotlib and Python Graph Gallery linksreferences/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 escapingexamples/visual/bar-chart.json -- PBIR visual.json: horizontal stacked bar with PY comparison lines and % change labelsexamples/visual/kpi-card.json -- PBIR visual.json: text-based KPI with value, % change indicator, and PY comparisonexamples/visual/trend-line.json -- PBIR visual.json: area chart with line plot and monthly x-axisTo 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.
pbi-report-design -- Layout and design best practicesr-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 referenceOther measured skills in the registry, with their headline benchmark lift.