Visualization and Dashboards
Qlik data models, Databricks Lakeview, matplotlib for briefings, and the design principles that separate a chart a general keeps from one that gets skipped.
matplotlib for Government Briefings
Government briefing charts have specific constraints: they're exported to PowerPoint or PDF, viewed on projectors with poor contrast, and reproduced in black-and-white in printed annexes. Configure matplotlib once and use the same style throughout a project.
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
# Government briefing style configuration
# Apply once at notebook/script start — all subsequent charts inherit this style
GOVT_COLORS = {
"primary": "#1B3A6B", # Navy blue
"secondary": "#C5A572", # Gold
"accent": "#8B0000", # Dark red for alerts/thresholds
"neutral": "#6B6B6B", # Gray for secondary information
"green": "#2E7D32", # Green for positive indicators
}
plt.rcParams.update({
"figure.facecolor": "white",
"axes.facecolor": "white",
"axes.grid": True,
"grid.alpha": 0.3,
"grid.linestyle": "--",
"font.family": "sans-serif",
"font.size": 11,
"axes.titlesize": 13,
"axes.titleweight": "bold",
"axes.labelsize": 11,
"xtick.labelsize": 10,
"ytick.labelsize": 10,
"legend.fontsize": 10,
"figure.dpi": 150,
"savefig.dpi": 300,
"savefig.bbox": "tight",
"savefig.facecolor": "white",
})
def threshold_timeseries(dates, values, threshold: float,
title: str, ylabel: str,
threshold_label: str = "Alert Threshold") -> plt.Figure:
"""
Standard government timeseries with threshold line.
Red above threshold, blue below. Used for readiness, budget, risk tracking.
"""
fig, ax = plt.subplots(figsize=(12, 5))
above = np.where(values >= threshold, values, np.nan)
below = np.where(values < threshold, values, np.nan)
ax.plot(dates, below, color=GOVT_COLORS["primary"], linewidth=2, label="Within threshold")
ax.plot(dates, above, color=GOVT_COLORS["accent"], linewidth=2, label="Above threshold")
ax.axhline(threshold, color=GOVT_COLORS["accent"], linestyle="--",
linewidth=1.5, label=threshold_label, alpha=0.8)
ax.fill_between(dates, threshold, values,
where=(values >= threshold), alpha=0.15,
color=GOVT_COLORS["accent"], label="_nolegend_")
ax.set_title(title)
ax.set_ylabel(ylabel)
ax.legend(loc="upper left")
ax.spines[["top", "right"]].set_visible(False)
return fig
Horizontal Bar Charts for Rankings
Horizontal bar charts are the standard for program-level comparisons, vendor rankings, and any list where labels are long. They're readable in PowerPoint at any font size and work in grayscale.
def ranked_bar_chart(labels: list, values: list,
title: str, xlabel: str,
n_highlight: int = 3,
format_fn=None) -> plt.Figure:
"""
Horizontal bar chart with top N highlighted.
Standard for program rankings in government briefings.
"""
# Sort descending
sorted_pairs = sorted(zip(labels, values), key=lambda x: x[1])
labels_sorted = [p[0] for p in sorted_pairs]
values_sorted = [p[1] for p in sorted_pairs]
colors = [GOVT_COLORS["accent"] if i >= len(values_sorted) - n_highlight
else GOVT_COLORS["primary"]
for i in range(len(values_sorted))]
fig, ax = plt.subplots(figsize=(10, max(4, len(labels) * 0.35)))
bars = ax.barh(labels_sorted, values_sorted, color=colors, height=0.6)
# Value labels on bars
for bar, val in zip(bars, values_sorted):
label = format_fn(val) if format_fn else f"{val:,.0f}"
ax.text(bar.get_width() * 1.01, bar.get_y() + bar.get_height() / 2,
label, va="center", ha="left", fontsize=9)
ax.set_title(title, fontweight="bold")
ax.set_xlabel(xlabel)
ax.spines[["top", "right", "left"]].set_visible(False)
ax.tick_params(left=False)
ax.set_xlim(right=max(values_sorted) * 1.15)
return fig
Plotly for Interactive Dashboards
Plotly produces interactive charts that run in any browser without a server. On Databricks, Plotly charts render natively in notebook output. For standalone deployment, Plotly Dash is the standard Python framework for operational dashboards behind the platform login.
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
def contract_risk_dashboard(df: pd.DataFrame) -> go.Figure:
"""
Interactive procurement risk dashboard for program review.
Shows risk distribution, vendor breakdown, and fiscal month trends.
"""
fig = make_subplots(
rows=2, cols=2,
subplot_titles=[
"Risk Score Distribution",
"Top 10 Vendors by Obligation",
"Monthly Obligation Trend (FY2024)",
"Risk Score by NAICS Sector",
],
specs=[
[{"type": "histogram"}, {"type": "bar"}],
[{"type": "scatter"}, {"type": "box"}],
]
)
# Risk score distribution
fig.add_trace(
go.Histogram(x=df["overrun_probability"], nbinsx=20,
marker_color="#1B3A6B", name="Risk Distribution"),
row=1, col=1
)
# Top vendors
top_vendors = df.groupby("recipient_name")["federal_action_obligation"].sum().nlargest(10)
fig.add_trace(
go.Bar(x=top_vendors.values / 1e6, y=top_vendors.index,
orientation="h", marker_color="#C5A572", name="Obligation ($M)"),
row=1, col=2
)
# Monthly trend
monthly = df.groupby("fiscal_month")["federal_action_obligation"].sum() / 1e9
fig.add_trace(
go.Scatter(x=list(monthly.index), y=list(monthly.values),
mode="lines+markers", marker_color="#1B3A6B",
name="Monthly Obligations ($B)"),
row=2, col=1
)
# Risk by sector
fig.add_trace(
go.Box(x=df["naics_sector_desc"], y=df["overrun_probability"],
marker_color="#1B3A6B", name="Risk by Sector"),
row=2, col=2
)
fig.update_layout(
title_text="Procurement Risk Dashboard — FY2024",
height=700,
showlegend=False,
template="simple_white",
)
return fig
dashboard = contract_risk_dashboard(df_scored)
dashboard.show()
Databricks Lakeview Dashboards
Databricks Lakeview (formerly "Databricks SQL Dashboards") provides browser-based dashboards that query Unity Catalog directly. On Advana and Jupiter, Lakeview dashboards are the standard delivery mechanism for recurring operational reports — they update automatically when new data lands in the Delta table, require no Python runtime to render, and share via link to anyone with platform access.
Lakeview dashboards are built through the Databricks UI — you don't write code. The key decision is which SQL query powers each widget. Write the SQL in a Databricks SQL notebook first, verify performance against your actual table (not a sample), and then connect it to the Lakeview dashboard. A query that takes 45 seconds on first run is unacceptable in an operational dashboard.
Qlik Sense Data Model Design
Qlik's QIX associative engine is powerful but requires a disciplined data model. Every selection in a Qlik app filters all other charts simultaneously — which is the feature, but only if the data model is designed to support it. The most common Qlik mistake in government deployments: building a data model that replicates a SQL report layout rather than designing for associative exploration.
# Qlik load script — star schema pattern for government procurement data
# One Fact table, multiple Dimension tables, named associations
Contracts:
LOAD
contract_award_unique_key AS ContractKey,
recipient_uei AS VendorKey,
naics_code AS NAICSKey,
awarding_agency_code AS AgencyKey,
federal_action_obligation,
fiscal_year,
fiscal_month,
action_date
FROM [lib://DataLake/silver/usaspending_fy2024.qvd] (qvd);
Vendors:
LOAD
recipient_uei AS VendorKey,
recipient_name,
cage_code,
business_type_desc
FROM [lib://DataLake/silver/sam_vendors.qvd] (qvd);
NAICSCodes:
LOAD
naics_code AS NAICSKey,
naics_description,
naics_sector_code,
naics_sector_desc
FROM [lib://Reference/naics_2022.qvd] (qvd);
Agencies:
LOAD
agency_code AS AgencyKey,
agency_name,
department_name,
cabinet_department
FROM [lib://Reference/agency_lookup.qvd] (qvd);
Palantir Slate for Operational Applications
Palantir Slate is the application builder inside Foundry. Unlike Qlik (read-only analytics) or a Databricks dashboard (read-only reporting), Slate applications are read-write — analysts can view predictions, review flagged items, approve or override decisions, and that action is captured as an Ontology Action with full audit trail. This is the critical distinction for operational government AI: the human-in-the-loop isn't optional, and Slate is where the loop closes.
Slate applications are built through the Foundry UI — drag-and-drop widget configuration backed by Ontology queries. The key technical integration point is the Action definition: when an analyst clicks "Approve" on a flagged work order, the Action writes a structured record to the Ontology with the analyst's identity, timestamp, and decision. Every audit trail query runs against this Action history, not against log files.
Design Checklist
- Chart exports cleanly to black-and-white (print/annex usability)
- Labels are readable at 1024x768 projection resolution
- Color scale is accessible to colorblind viewers (use blue/orange, not red/green alone)
- All axes labeled with units ($, %, days, etc.)
- Title states the finding, not the topic ("Vendor A has highest cost growth", not "Cost Growth by Vendor")
- Threshold lines labeled with the threshold value and authority
- Data source and as-of date visible on every chart delivered to leadership
- Dashboard query performance tested on production table size, not sample
Where This Goes Wrong
Failure Mode 1: Dashboard That Requires Explanation to Read
A chart that the data scientist can interpret but the program manager cannot — requires verbal explanation at every briefing, meaning the visual is not doing its job. Fix: test every chart by showing it without explanation to one non-technical stakeholder. If they can't state the main finding in 10 seconds, redesign.
Failure Mode 2: Building a Qlik App Like a SQL Report
Replicating fixed-format Crystal Reports or SSRS reports in Qlik with pre-defined filter dropdowns. The QIX engine's associative capability is never used. Fix: design the data model first, dashboards second. Build a model at the right grain with clean associations, then let analysts explore rather than just view pre-specified filters.
Failure Mode 3: Slow Dashboard Queries
A Lakeview or Qlik dashboard that takes 45 seconds to load because the underlying query runs a full table scan on 50 million rows without optimization. Fix: materialize frequently-queried aggregates as Gold-tier Delta tables. Set query result caching in Databricks SQL. Use QVD incremental loads in Qlik to avoid full database refreshes on every reload.
Platform Comparison
| Capability | Databricks Lakeview | Qlik Sense | Palantir Slate | Plotly Dash |
|---|---|---|---|---|
| Data model | SQL on Delta/Unity Catalog | QIX associative (in-memory) | Ontology Objects | Python (any source) |
| Read-write actions | No | No (limited write via APIs) | Yes (Ontology Actions) | Yes (custom callbacks) |
| Associative exploration | No | Best-in-class | Via Workshop filters | Custom implementation |
| FedRAMP / IL authorization | FedRAMP High (IL5) | FedRAMP Moderate (IL4) | FedRAMP High (IL5) | Depends on hosting |
| No-code dashboard building | Yes (UI) | Yes (drag-and-drop) | Yes (widget-based) | No (Python code) |
Exercises
This chapter includes 6 hands-on exercises with full solutions — coding challenges, analysis tasks, and scenario-based problems.
View Exercises on GitHub →