Chapter 10

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.

~45 min read Databricks, Qlik, Palantir Foundry/Slate, Advana, Navy Jupiter Code on GitHub

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.

python
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.

python
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.

python
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.

bash
# 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 →