53  Visual Design System

Color schemes, Plotly templates, and styling conventions

54 Introduction

The Aquifer Intelligence Playbook uses a custom visual design system to ensure consistency across all visualizations, documentation, and interactive components. This guide provides reference for extending chapters or building new artifacts that align with our visual standards.

This guide covers: - Color palettes and theme tokens - Plotly visualization templates - Quarto book styling conventions - Reusable UI components - Figure and asset organization


54.1 Color Palettes

54.1.1 Primary Palette (Book-Wide)

The base color scheme reflects water and earth tones:

Token Hex Usage
--aquifer-navy #1b2a4b Headings, primary text, high-contrast elements
--aquifer-sky #3cb4e5 Accents, hyperlinks, interactive elements
--aquifer-mint #4bd5b3 Highlights, success states, positive indicators
--aquifer-sand #f5f7fa Background, light surfaces
--aquifer-steel #64748b Secondary text, borders

Usage Guidelines: - Use --aquifer-navy for high contrast and readability - Reserve --aquifer-sky for clickable/interactive elements - Use --aquifer-mint sparingly for emphasis - --aquifer-sand provides subtle backgrounds


54.1.2 Part-Specific Colors

Each part of the playbook has a unique accent color for visual navigation:

Part Color Name Hex Usage
Part 1 Azure Blue #2e8bcc Foundations - Individual analysis
Part 2 Lagoon Teal #18b8c9 Spatial Patterns - 2D/3D analysis
Part 3 Mint Green #3cd4a8 Temporal Dynamics - Time series
Part 4 Purple #7c3aed Data Fusion - Integration
Part 5 Amber #f59e0b Operations - Production systems
Part 6 Pink #ec4899 Knowledge - Synthesis & learning
Assets Slate Gray #64748b Shared resources

Implementation: - Colors automatically applied via part-colors.js - Affects navigation, table of contents, and in-page elements - No manual color coding required in chapters


54.2 HTEM Visualization Scales

54.2.1 Material Type Color Scale

For HTEM material type visualizations:

# Sediment Quality Scale (Low to High)
material_colors = {
    1: '#8B4513',   # Clay - Dark brown
    2: '#A0522D',   # Very poorly sorted - Sienna
    3: '#BC8F8F',   # Fine diamicton - Rosy brown
    5: '#D2B48C',   # Poorly sorted - Tan
    8: '#F4A460',   # Moderately sorted - Sandy brown
    11: '#FFD700',  # Well sorted - Gold
    13: '#FFA500',  # Very well sorted - Orange
    14: '#FF8C00'   # Extremely well sorted - Dark orange
}

# Bedrock Scale
bedrock_colors = {
    101: '#696969',  # Carboniferous shale
    102: '#778899',  # Carboniferous sandstone
    103: '#A9A9A9',  # Carboniferous margins
    104: '#C0C0C0',  # Carbonate margins
    105: '#D3D3D3'   # Carbonate
}

54.2.2 Resistivity Color Scale

For continuous resistivity visualizations (Plotly):

# Diverging scale for resistivity values
resistivity_colorscale = [
    [0.0, '#2c3e50'],   # Low (Clay) - Dark blue-gray
    [0.2, '#3498db'],   # Low-medium - Blue
    [0.4, '#1abc9c'],   # Medium - Teal
    [0.6, '#f39c12'],   # Medium-high - Orange
    [0.8, '#e74c3c'],   # High - Red
    [1.0, '#c0392b']    # Very high (Bedrock) - Dark red
]

Usage in Plotly:

import plotly.graph_objects as go

fig = go.Figure(data=go.Heatmap(
    z=resistivity_data,
    colorscale=resistivity_colorscale,
    colorbar=dict(title='Resistivity (Ω·m)')
))

54.3 Plotly Visualization Templates

54.3.1 Standard Template

Use this template for all Plotly visualizations to ensure consistency:

import plotly.graph_objects as go
import plotly.io as pio

# Define custom template
aquifer_template = go.layout.Template(
    layout=dict(
        # Fonts
        font=dict(
            family="Inter, system-ui, -apple-system, sans-serif",
            size=12,
            color="#1b2a4b"
        ),
        title=dict(
            font=dict(size=18, color="#1b2a4b"),
            x=0.5,
            xanchor='center'
        ),

        # Background colors
        paper_bgcolor="#ffffff",
        plot_bgcolor="#f5f7fa",

        # Grid
        xaxis=dict(
            gridcolor="#e2e8f0",
            showgrid=True,
            zeroline=False
        ),
        yaxis=dict(
            gridcolor="#e2e8f0",
            showgrid=True,
            zeroline=False
        ),

        # Color sequence
        colorway=['#2e8bcc', '#18b8c9', '#3cd4a8', '#7c3aed', '#f59e0b', '#ec4899'],

        # Margins
        margin=dict(l=60, r=40, t=60, b=60),

        # Modebar
        modebar=dict(
            bgcolor='rgba(255,255,255,0.8)',
            color='#64748b',
            activecolor='#2e8bcc'
        )
    )
)

# Register template
pio.templates["aquifer"] = aquifer_template
pio.templates.default = "aquifer"

Usage: Copy this template into your analysis scripts or save to a local plotly_theme.py file. This provides a reference implementationβ€”adapt as needed for your specific visualizations.


54.3.2 2D Spatial Plot Template

For HTEM grid visualizations:

def create_spatial_plot(x, y, z, title, colorbar_title):
    """Create standardized 2D spatial plot."""
    fig = go.Figure(data=go.Heatmap(
        x=x,
        y=y,
        z=z,
        colorscale=resistivity_colorscale,
        colorbar=dict(
            title=colorbar_title,
            titleside='right',
            tickmode='linear',
            tick0=0,
            dtick=50
        )
    ))

    fig.update_layout(
        title=title,
        xaxis_title="Easting (m)",
        yaxis_title="Northing (m)",
        xaxis=dict(scaleanchor="y", scaleratio=1),  # Equal aspect ratio
        width=800,
        height=700
    )

    return fig

54.3.3 Time Series Plot Template

For temporal analysis:

def create_timeseries_plot(dates, values, title, y_label):
    """Create standardized time series plot."""
    fig = go.Figure()

    fig.add_trace(go.Scatter(
        x=dates,
        y=values,
        mode='lines',
        line=dict(color='#2e8bcc', width=2),
        name='Observed'
    ))

    fig.update_layout(
        title=title,
        xaxis_title="Date",
        yaxis_title=y_label,
        hovermode='x unified',
        showlegend=True,
        width=1000,
        height=500
    )

    return fig

54.3.4 3D Visualization Template

For subsurface models:

def create_3d_plot(x, y, z, values, title):
    """Create standardized 3D scatter plot."""
    fig = go.Figure(data=go.Scatter3d(
        x=x,
        y=y,
        z=z,
        mode='markers',
        marker=dict(
            size=2,
            color=values,
            colorscale=resistivity_colorscale,
            showscale=True,
            colorbar=dict(title='Resistivity (Ω·m)')
        )
    ))

    fig.update_layout(
        title=title,
        scene=dict(
            xaxis_title="Easting (m)",
            yaxis_title="Northing (m)",
            zaxis_title="Elevation (m)",
            aspectmode='data'
        ),
        width=900,
        height=700
    )

    return fig

54.4 Quarto Book Styling

54.4.1 CSS Variables

Defined in aquifer-book/styles.css:

:root {
  /* Colors */
  --aquifer-navy: #1b2a4b;
  --aquifer-sky: #3cb4e5;
  --aquifer-mint: #4bd5b3;
  --aquifer-sand: #f5f7fa;
  --aquifer-steel: #64748b;

  /* Typography */
  --font-family-sans: "Inter", system-ui, -apple-system, sans-serif;
  --font-family-mono: "Fira Code", "Consolas", monospace;

  /* Spacing */
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;

  /* Border radius */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;
}

54.4.2 Custom Components

Aquifer Banner

Use for chapter introductions:

::: {.aquifer-banner}
# Chapter Title

Brief description of what this chapter covers and why it matters.
:::

CSS:

.aquifer-banner {
  background: linear-gradient(135deg, var(--aquifer-navy) 0%, var(--aquifer-sky) 100%);
  color: white;
  padding: var(--spacing-xl);
  border-radius: var(--radius-lg);
  margin-bottom: var(--spacing-lg);
}

Workflow Grid

For displaying phase overviews or multi-step processes:

::: {.workflow-grid}
::: {.workflow-card}
### Step 1
Description
:::

::: {.workflow-card}
### Step 2
Description
:::

::: {.workflow-card}
### Step 3
Description
:::
:::

CSS:

.workflow-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: var(--spacing-md);
  margin: var(--spacing-lg) 0;
}

.workflow-card {
  background: var(--aquifer-sand);
  border: 1px solid #e2e8f0;
  border-radius: var(--radius-md);
  padding: var(--spacing-md);
}

Callout Styles

Standard Quarto callouts with custom styling:

::: {.callout-note icon=false}
## πŸ’» For Computer Scientists
CS-specific content here
:::

::: {.callout-tip icon=false}
## 🌍 For Hydrogeologists
Hydro-specific content here
:::

::: {.callout-important icon=false}
## πŸ“Š For Statisticians
Stats-specific content here
:::

::: {.callout-warning icon=false}
## ⚠️ Critical Issue
Important warnings
:::

54.4.3 Code Block Styling

Syntax highlighting uses Monokai theme with custom tweaks:

# In _quarto.yml
format:
  html:
    highlight-style: monokai
    code-copy: true
    code-line-numbers: true

Custom code block additions:

pre.sourceCode {
  background: #272822 !important;
  border-radius: var(--radius-md);
  padding: var(--spacing-md);
}

code {
  font-family: var(--font-family-mono);
  font-size: 0.9em;
}

54.5 Figure Organization

54.5.1 Directory Structure

aquifer-book/
β”œβ”€β”€ assets/
β”‚   └── figures/
β”‚       β”œβ”€β”€ part-1/           # Foundations figures
β”‚       β”œβ”€β”€ part-2/           # Spatial figures
β”‚       β”œβ”€β”€ part-3/           # Temporal figures
β”‚       β”œβ”€β”€ part-4/           # Fusion figures
β”‚       β”œβ”€β”€ part-5/           # Operations figures
β”‚       β”œβ”€β”€ part-6/           # Knowledge figures
β”‚       └── shared/           # Cross-cutting figures

54.5.2 Naming Conventions

Static images:

{part}-{chapter}-{description}-{version}.{ext}

Examples:
- part1-htem-resistivity-distribution-v1.png
- part2-spatial-variogram-analysis-v2.png
- part3-timeseries-decomposition-final.png

Interactive HTML figures:

{part}-{chapter}-{description}-interactive.html

Examples:
- part2-spatial-3d-aquifer-model-interactive.html
- part4-fusion-correlation-matrix-interactive.html

54.5.3 Figure Metadata

Include metadata table in chapters for reproducibility:

| Figure | Source Script | Data Version | Last Updated |
|--------|--------------|--------------|--------------|
| Figure 2.1 | `scripts/spatial_analysis.py` | 2024-2025 | 2025-10-31 |
| Figure 2.2 | `notebooks/02_Spatial_Stats.ipynb` | 2024-2025 | 2025-10-30 |

54.6 Interactive Component Guidelines

54.6.1 Plotly Interactivity

Standard modebar buttons:

fig.update_layout(
    modebar=dict(
        buttons=[
            'zoom2d', 'pan2d', 'zoomIn2d', 'zoomOut2d',
            'autoScale2d', 'resetScale2d',
            'toImage'
        ]
    )
)

Hover templates:

hovertemplate = (
    "<b>Location</b>: (%{x:.0f}, %{y:.0f})<br>"
    "<b>Resistivity</b>: %{z:.1f} Ω·m<br>"
    "<extra></extra>"  # Removes trace name
)

fig.update_traces(hovertemplate=hovertemplate)

54.6.2 Responsive Design

Ensure visualizations work on different screen sizes:

fig.update_layout(
    autosize=True,
    width=None,  # Let container define width
    height=600,  # Fixed height for consistency

    # Mobile-friendly margins
    margin=dict(
        l=50 if is_mobile else 60,
        r=30 if is_mobile else 40,
        t=50 if is_mobile else 60,
        b=50 if is_mobile else 60
    )
)

54.7 Typography Standards

54.7.1 Headings

# Part Title (H1)
## Chapter Title (H2)
### Major Section (H3)
#### Subsection (H4)
##### Minor Subsection (H5)

Guidelines: - H1 only for part titles - H2 for chapter titles - H3 for major sections within chapters - Avoid going deeper than H5


54.7.2 Text Formatting

Emphasis: - **bold** for important terms on first use - *italic* for emphasis or introducing new concepts - `code` for variable names, function names, file paths

Lists: - Use bullet points for unordered lists - Use numbered lists for sequential steps - Indent nested lists consistently

Links:

[Descriptive link text](url)
[See Temporal Overview](../part-3-temporal/overview.qmd)

54.8 Accessibility Standards

NoteFor Newcomers: Why Accessibility Matters

Accessibility means designing so everyone can use the playbook, regardless of:

  • Visual abilities (color blindness, low vision, blindness)
  • Motor abilities (using keyboard vs. mouse)
  • Cognitive preferences (different learning styles)

Good accessibility benefits everyone:

  • High contrast text is easier to read for all users
  • Keyboard navigation helps power users work faster
  • Alt text helps when images don’t load
  • Clear descriptions help when you’re learning complex topics

54.8.1 Color Contrast

All color combinations meet WCAG 2.1 AA standards:

  • Minimum contrast ratio: 4.5:1 for normal text
  • Minimum contrast ratio: 3:1 for large text
  • --aquifer-navy on white: 12.6:1 βœ“
  • --aquifer-sky on white: 3.8:1 βœ“ (large text only)
TipWhat This Means

Contrast ratio measures how easy it is to distinguish text from background:

  • Higher ratio = easier to read (even in bright sunlight or for people with low vision)
  • WCAG AA is an international standard for web accessibility
  • 4.5:1 minimum ensures most people can read normal-sized text
  • 3:1 for large text (18pt+ or 14pt+ bold) is acceptable for headings

Our navy text on white background (12.6:1) exceeds minimum by 2.8Γ—, making it very easy to read.


54.8.2 Alternative Text

Always provide alt text for images:

![2D resistivity map showing high-resistivity sand channels](figures/resistivity-map.png)

For complex figures, provide detailed descriptions:

::: {.figure-description}
**Figure 2.3** shows a 2D cross-section of resistivity values from 0-100m depth.
High resistivity zones (>100 Ω·m, shown in orange-red) indicate sandy aquifer
materials at 15-25m depth. Low resistivity zones (<30 Ω·m, shown in blue)
indicate clay-rich confining layers.
:::

54.8.3 Keyboard Navigation

Ensure all interactive elements are keyboard-accessible:

  • Plotly charts: Use built-in keyboard support
  • Custom buttons: Add tabindex="0" and keyboard event handlers
  • Navigation: Test with Tab, Enter, Escape keys

54.9 Export Formats

54.9.1 Figure Export Settings

For publication:

fig.write_image(
    "figure.png",
    width=1200,
    height=800,
    scale=2  # 2x resolution for high DPI displays
)

fig.write_image(
    "figure.pdf",
    width=1200,
    height=800
)

For web:

fig.write_html(
    "figure.html",
    include_plotlyjs='cdn',  # Smaller file size
    config={'displayModeBar': True}
)

54.10 Extending the Theme

54.10.1 Adding New Colors

  1. Add to CSS variables in styles.css
  2. Document in this guide
  3. Update plotly_theme.py if applicable
  4. Test contrast ratios

54.10.2 Creating New Components

  1. Design in HTML/CSS
  2. Add to styles.css
  3. Document usage in this guide
  4. Provide example in dedicated demo chapter

54.10.3 Modifying Plotly Template

  1. Edit src/visualization/plotly_theme.py
  2. Test across all visualization types
  3. Update this guide with changes
  4. Regenerate affected figures

54.11 Common Patterns

54.11.1 Side-by-Side Figures

::: {.grid}
::: {.g-col-6}
![Caption A](figure-a.png)
:::

::: {.g-col-6}
![Caption B](figure-b.png)
:::
:::

54.11.2 Figure with Caption and Source Attribution

![Resistivity distribution for Unit D](figures/unit-d-histogram.png)

**Figure 2.5.** Distribution of resistivity values in Unit D (primary aquifer).
High-resistivity tail (>150 Ω·m) indicates well-sorted sand bodies.

*Source:* `scripts/comprehensive_htem_analysis.py`, Module 3

54.11.3 Multi-Panel Dashboard Layout

from plotly.subplots import make_subplots

fig = make_subplots(
    rows=2, cols=2,
    subplot_titles=('Panel A', 'Panel B', 'Panel C', 'Panel D'),
    specs=[
        [{'type': 'scatter'}, {'type': 'bar'}],
        [{'type': 'heatmap', 'colspan': 2}, None]
    ]
)

# Add traces to each panel
fig.add_trace(go.Scatter(...), row=1, col=1)
fig.add_trace(go.Bar(...), row=1, col=2)
fig.add_trace(go.Heatmap(...), row=2, col=1)

fig.update_layout(height=800, showlegend=False)

54.12 Quality Checklist

Before committing visualizations, verify:


54.13 Resources

54.13.1 Design Tools

54.13.2 Inspiration

  • Matplotlib Gallery: For layout ideas
  • Observable HQ: For interactive dashboard concepts
  • R Shiny Gallery: For application design patterns

54.14 Contributing to the Theme

Found a better color scheme? Want to add a new component? Contributions welcome!

Process: 1. Prototype your change 2. Test across multiple chapters 3. Update this guide with documentation 4. Submit PR with examples

Questions? Open an issue with the design label.


Last Updated: November 26, 2025 Maintainers: Design team (open to contributors) License: MIT (same as project)