or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

discretization.mdexport.mdfile-io.mdindex.mdmodflow2005.mdmodflow6.mdparticle-tracking.mdplotting.mdtransport.mdutilities.md

plotting.mddocs/

0

# Visualization and Plotting

1

2

This module provides comprehensive plotting utilities for map views, cross-sections, and 3D visualization with matplotlib integration and export capabilities. These tools enable visualization of model grids, input arrays, results, and analysis outputs for both structured and unstructured grids.

3

4

## Map View Plotting

5

6

### PlotMapView

7

8

Main class for creating map view plots of MODFLOW models and results.

9

10

```python { .api }

11

class PlotMapView:

12

"""Map view plotting for MODFLOW models"""

13

def __init__(

14

self,

15

model: object = None,

16

ax: object = None,

17

layer: int = 0,

18

extent: list[float] = None,

19

modelgrid: object = None,

20

**kwargs

21

): ...

22

23

def plot_grid(

24

self,

25

**kwargs

26

) -> object:

27

"""Plot model grid"""

28

...

29

30

def plot_array(

31

self,

32

a: np.ndarray,

33

masked_values: list = None,

34

**kwargs

35

) -> object:

36

"""Plot 2D array data"""

37

...

38

39

def contour_array(

40

self,

41

a: np.ndarray,

42

masked_values: list = None,

43

tri_mask: bool = False,

44

**kwargs

45

) -> object:

46

"""Create contour plot of array data"""

47

...

48

49

def plot_ibound(

50

self,

51

ibound: np.ndarray = None,

52

color_noflow: str = 'black',

53

color_ch: str = 'blue',

54

color_vpt: str = 'red',

55

color_active: str = None,

56

**kwargs

57

) -> object:

58

"""Plot ibound array"""

59

...

60

61

def plot_inactive(

62

self,

63

ibound: np.ndarray = None,

64

color_noflow: str = 'black',

65

**kwargs

66

) -> object:

67

"""Plot inactive cells"""

68

...

69

70

def plot_bc(

71

self,

72

name: str = None,

73

package: object = None,

74

kper: int = 0,

75

color: str = None,

76

plotAll: bool = False,

77

boundname: str = None,

78

**kwargs

79

) -> object:

80

"""Plot boundary conditions"""

81

...

82

83

def plot_shapefile(

84

self,

85

shp,

86

**kwargs

87

) -> object:

88

"""Plot shapefile data"""

89

...

90

91

def plot_shapes(

92

self,

93

obj,

94

**kwargs

95

) -> object:

96

"""Plot geometric shapes"""

97

...

98

99

def plot_centers(

100

self,

101

**kwargs

102

) -> object:

103

"""Plot cell centers"""

104

...

105

106

def plot_pathline(

107

self,

108

pl: list = None,

109

travel_time: np.ndarray = None,

110

**kwargs

111

) -> object:

112

"""Plot MODPATH pathlines"""

113

...

114

115

def plot_endpoint(

116

self,

117

ep: np.ndarray = None,

118

direction: str = 'ending',

119

selection: np.ndarray = None,

120

**kwargs

121

) -> object:

122

"""Plot MODPATH endpoints"""

123

...

124

125

def plot_timeseries(

126

self,

127

ts: np.ndarray = None,

128

travel_time: float = None,

129

**kwargs

130

) -> object:

131

"""Plot MODPATH timeseries data"""

132

...

133

134

def plot_vector(

135

self,

136

vx: np.ndarray,

137

vy: np.ndarray,

138

istep: int = 1,

139

jstep: int = 1,

140

normalize: bool = False,

141

masked_values: list = None,

142

**kwargs

143

) -> object:

144

"""Plot vector field"""

145

...

146

147

@property

148

def extent(self) -> tuple[float, float, float, float]:

149

"""Plot extent (xmin, xmax, ymin, ymax)"""

150

...

151

152

@property

153

def mg(self) -> object:

154

"""Model grid object"""

155

...

156

```

157

158

## Cross-Section Plotting

159

160

### PlotCrossSection

161

162

Class for creating cross-sectional plots through MODFLOW models.

163

164

```python { .api }

165

class PlotCrossSection:

166

"""Cross-section plotting for MODFLOW models"""

167

def __init__(

168

self,

169

model: object = None,

170

ax: object = None,

171

line: dict = None,

172

extent: list[float] = None,

173

modelgrid: object = None,

174

geographic_coords: bool = False,

175

**kwargs

176

): ...

177

178

def plot_grid(

179

self,

180

**kwargs

181

) -> object:

182

"""Plot cross-section grid"""

183

...

184

185

def plot_array(

186

self,

187

a: np.ndarray,

188

masked_values: list = None,

189

**kwargs

190

) -> object:

191

"""Plot array data in cross-section"""

192

...

193

194

def contour_array(

195

self,

196

a: np.ndarray,

197

masked_values: list = None,

198

**kwargs

199

) -> object:

200

"""Create contour plot in cross-section"""

201

...

202

203

def plot_surface(

204

self,

205

a: np.ndarray,

206

masked_values: list = None,

207

**kwargs

208

) -> object:

209

"""Plot surface elevation"""

210

...

211

212

def plot_fill_between(

213

self,

214

a: np.ndarray,

215

colors: list = None,

216

masked_values: list = None,

217

**kwargs

218

) -> object:

219

"""Plot filled areas between surfaces"""

220

...

221

222

def plot_bc(

223

self,

224

name: str = None,

225

package: object = None,

226

kper: int = 0,

227

color: str = 'red',

228

**kwargs

229

) -> object:

230

"""Plot boundary conditions in cross-section"""

231

...

232

233

def plot_ibound(

234

self,

235

ibound: np.ndarray = None,

236

color_noflow: str = 'black',

237

color_ch: str = 'blue',

238

**kwargs

239

) -> object:

240

"""Plot ibound in cross-section"""

241

...

242

243

def plot_discharge(

244

self,

245

frf: np.ndarray,

246

fff: np.ndarray,

247

flf: np.ndarray = None,

248

head: np.ndarray = None,

249

normalize: bool = False,

250

**kwargs

251

) -> object:

252

"""Plot discharge vectors in cross-section"""

253

...

254

255

def plot_pathline(

256

self,

257

pl: list = None,

258

travel_time: np.ndarray = None,

259

**kwargs

260

) -> object:

261

"""Plot pathlines in cross-section"""

262

...

263

264

def plot_endpoint(

265

self,

266

ep: np.ndarray = None,

267

direction: str = 'ending',

268

**kwargs

269

) -> object:

270

"""Plot endpoints in cross-section"""

271

...

272

273

@property

274

def extent(self) -> tuple[float, float, float, float]:

275

"""Cross-section extent"""

276

...

277

278

@property

279

def direction(self) -> str:

280

"""Cross-section direction"""

281

...

282

```

283

284

## Utility Classes

285

286

### PlotUtilities

287

288

General plotting utilities and helper functions.

289

290

```python { .api }

291

class PlotUtilities:

292

"""General plotting utilities"""

293

294

@staticmethod

295

def plot_text(

296

ax: object,

297

model: object = None,

298

text_dict: dict = None,

299

**kwargs

300

) -> None:

301

"""Add text to plot"""

302

...

303

304

@staticmethod

305

def plot_shapefile(

306

shp: str,

307

ax: object = None,

308

**kwargs

309

) -> object:

310

"""Plot shapefile data"""

311

...

312

313

@staticmethod

314

def add_colorbar(

315

mappable: object,

316

ax: object = None,

317

**kwargs

318

) -> object:

319

"""Add colorbar to plot"""

320

...

321

322

@staticmethod

323

def set_aspect(

324

ax: object,

325

aspect: str = 'equal',

326

**kwargs

327

) -> None:

328

"""Set plot aspect ratio"""

329

...

330

```

331

332

### SwiConcentration

333

334

Specialized plotting for seawater intrusion concentration data.

335

336

```python { .api }

337

class SwiConcentration:

338

"""Seawater intrusion concentration plotting"""

339

def __init__(

340

self,

341

model: object = None,

342

botm: np.ndarray = None,

343

istrat: int = 1,

344

nu: np.ndarray = None,

345

**kwargs

346

): ...

347

348

def plot_concentration(

349

self,

350

zeta: np.ndarray,

351

layer: int = None,

352

**kwargs

353

) -> object:

354

"""Plot concentration distribution"""

355

...

356

357

def plot_zeta_surfaces(

358

self,

359

zeta: np.ndarray,

360

**kwargs

361

) -> object:

362

"""Plot zeta surfaces"""

363

...

364

```

365

366

## Standalone Plotting Functions

367

368

```python { .api }

369

def plot_shapefile(

370

shp: str,

371

ax: object = None,

372

layer: str = None,

373

**kwargs

374

) -> object:

375

"""Plot shapefile data on map"""

376

...

377

378

def shapefile_extents(shp: str) -> tuple[float, float, float, float]:

379

"""Get shapefile spatial extents"""

380

...

381

382

def plot_cvfd(

383

verts: np.ndarray,

384

iverts: list,

385

xc: np.ndarray = None,

386

yc: np.ndarray = None,

387

head: np.ndarray = None,

388

**kwargs

389

) -> object:

390

"""Plot control volume finite difference grid"""

391

...

392

```

393

394

## Styling and Formatting

395

396

### Plotting Styles

397

398

Predefined plotting styles and color schemes.

399

400

```python { .api }

401

# Style constants and color maps

402

styles = {

403

'default': {

404

'grid_color': 'black',

405

'grid_linewidth': 0.5,

406

'inactive_color': 'gray',

407

'bc_colors': {

408

'WEL': 'red',

409

'DRN': 'yellow',

410

'RIV': 'blue',

411

'GHB': 'cyan',

412

'CHD': 'navy'

413

}

414

},

415

'publication': {

416

'grid_color': 'black',

417

'grid_linewidth': 0.3,

418

'font_size': 12,

419

'title_size': 14

420

},

421

'presentation': {

422

'grid_color': 'gray',

423

'grid_linewidth': 1.0,

424

'font_size': 14,

425

'title_size': 18

426

}

427

}

428

429

def set_style(style_name: str) -> None:

430

"""Set plotting style"""

431

...

432

433

def get_color_palette(name: str) -> list[str]:

434

"""Get predefined color palette"""

435

...

436

```

437

438

## Usage Examples

439

440

### Basic Model Visualization

441

442

```python

443

import flopy

444

import flopy.utils as fpu

445

import matplotlib.pyplot as plt

446

import numpy as np

447

448

# Load or create MODFLOW model

449

mf = flopy.modflow.Modflow.load('model.nam')

450

451

# Create map view plot

452

fig, ax = plt.subplots(figsize=(12, 10))

453

mapview = flopy.plot.PlotMapView(model=mf, ax=ax, layer=0)

454

455

# Plot model grid

456

mapview.plot_grid(lw=0.5, color='gray')

457

458

# Plot boundary conditions

459

mapview.plot_bc('WEL', color='red', s=50) # Wells

460

mapview.plot_bc('RIV', color='blue', lw=3) # Rivers

461

mapview.plot_bc('GHB', color='cyan', lw=2) # General head boundaries

462

463

# Plot head results if available

464

try:

465

hds = fpu.HeadFile('model.hds')

466

head = hds.get_data(kstpkper=(0, 0))

467

468

# Contour heads

469

contours = mapview.contour_array(

470

head,

471

levels=np.linspace(head.min(), head.max(), 20),

472

colors='black',

473

linewidths=1

474

)

475

ax.clabel(contours, inline=True, fontsize=8, fmt='%.1f')

476

477

# Fill contours with colors

478

filled = mapview.plot_array(

479

head,

480

cmap='viridis',

481

alpha=0.7

482

)

483

cb = plt.colorbar(filled, ax=ax, shrink=0.8)

484

cb.set_label('Head Elevation (m)', fontsize=12)

485

486

hds.close()

487

488

except FileNotFoundError:

489

print("Head file not found, skipping head contours")

490

491

# Plot inactive cells

492

if hasattr(mf, 'bas6'):

493

mapview.plot_inactive(ibound=mf.bas6.ibound.array, color_noflow='gray')

494

495

# Add title and labels

496

ax.set_title('MODFLOW Model - Layer 1 Heads', fontsize=14, fontweight='bold')

497

ax.set_xlabel('X Coordinate (m)', fontsize=12)

498

ax.set_ylabel('Y Coordinate (m)', fontsize=12)

499

500

# Set equal aspect ratio

501

ax.set_aspect('equal')

502

503

plt.tight_layout()

504

plt.show()

505

```

506

507

### Flow Vector Visualization

508

509

```python

510

import flopy

511

import flopy.utils as fpu

512

import matplotlib.pyplot as plt

513

import numpy as np

514

515

# Load model and results

516

mf = flopy.modflow.Modflow.load('model.nam')

517

518

# Read budget file for flow data

519

cbb = fpu.CellBudgetFile('model.cbb')

520

frf = cbb.get_data(text='FLOW RIGHT FACE')[0] # Flow right face

521

fff = cbb.get_data(text='FLOW FRONT FACE')[0] # Flow front face

522

flf = cbb.get_data(text='FLOW LOWER FACE')[0] # Flow lower face (optional)

523

524

# Read head data

525

hds = fpu.HeadFile('model.hds')

526

head = hds.get_data(kstpkper=(0, 0))

527

528

# Create map view

529

fig, ax = plt.subplots(figsize=(14, 10))

530

mapview = flopy.plot.PlotMapView(model=mf, ax=ax, layer=0)

531

532

# Plot head contours as background

533

head_contours = mapview.contour_array(

534

head,

535

levels=15,

536

colors='gray',

537

alpha=0.5,

538

linewidths=0.8

539

)

540

541

# Plot discharge vectors

542

discharge_plot = mapview.plot_discharge(

543

frf, fff, flf,

544

head=head,

545

normalize=True, # Normalize vectors for better visualization

546

color='blue',

547

scale=50, # Scale factor for arrow size

548

headwidth=3,

549

headlength=2,

550

width=0.002

551

)

552

553

# Plot model grid (light)

554

mapview.plot_grid(lw=0.3, color='lightgray', alpha=0.5)

555

556

# Plot boundary conditions

557

mapview.plot_bc('WEL', color='red', marker='o', s=100, label='Wells')

558

mapview.plot_bc('RIV', color='cyan', lw=4, alpha=0.7, label='Rivers')

559

560

# Add legend

561

ax.legend(loc='upper right')

562

563

ax.set_title('Groundwater Flow Vectors', fontsize=16, fontweight='bold')

564

ax.set_xlabel('X Coordinate (m)', fontsize=12)

565

ax.set_ylabel('Y Coordinate (m)', fontsize=12)

566

567

plt.tight_layout()

568

plt.show()

569

570

# Close files

571

cbb.close()

572

hds.close()

573

```

574

575

### Cross-Section Visualization

576

577

```python

578

import flopy

579

import flopy.utils as fpu

580

import matplotlib.pyplot as plt

581

import numpy as np

582

583

# Load model

584

mf = flopy.modflow.Modflow.load('model.nam')

585

586

# Define cross-section line

587

line = {'line': [(0, 2500), (5000, 2500)]} # West-east cross-section

588

589

# Create cross-section plot

590

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 12))

591

592

# First subplot - Head distribution

593

xs1 = flopy.plot.PlotCrossSection(model=mf, ax=ax1, line=line)

594

595

# Read and plot heads

596

try:

597

hds = fpu.HeadFile('model.hds')

598

head = hds.get_data(kstpkper=(0, 0))

599

600

# Plot head array

601

head_plot = xs1.plot_array(

602

head,

603

cmap='RdYlBu',

604

alpha=0.8

605

)

606

607

# Add colorbar

608

cb1 = plt.colorbar(head_plot, ax=ax1, shrink=0.8)

609

cb1.set_label('Head Elevation (m)', fontsize=12)

610

611

# Contour heads

612

head_contours = xs1.contour_array(

613

head,

614

levels=10,

615

colors='black',

616

linewidths=1

617

)

618

ax1.clabel(head_contours, inline=True, fontsize=8)

619

620

hds.close()

621

622

except FileNotFoundError:

623

print("Head file not found")

624

625

# Plot grid and model layers

626

xs1.plot_grid(lw=1, color='black')

627

628

# Plot ibound

629

if hasattr(mf, 'bas6'):

630

xs1.plot_ibound(

631

ibound=mf.bas6.ibound.array,

632

color_noflow='gray',

633

alpha=0.5

634

)

635

636

ax1.set_title('Cross-Section: Head Distribution', fontsize=14, fontweight='bold')

637

ax1.set_ylabel('Elevation (m)', fontsize=12)

638

639

# Second subplot - Hydraulic conductivity

640

xs2 = flopy.plot.PlotCrossSection(model=mf, ax=ax2, line=line)

641

642

# Plot hydraulic conductivity

643

if hasattr(mf, 'lpf'):

644

k_array = mf.lpf.hk.array

645

k_plot = xs2.plot_array(

646

k_array,

647

cmap='plasma',

648

norm=plt.Normalize(vmin=k_array.min(), vmax=k_array.max()),

649

alpha=0.8

650

)

651

652

cb2 = plt.colorbar(k_plot, ax=ax2, shrink=0.8)

653

cb2.set_label('Hydraulic Conductivity (m/d)', fontsize=12)

654

655

# Plot grid

656

xs2.plot_grid(lw=1, color='black')

657

658

# Plot boundary conditions in cross-section

659

xs2.plot_bc('WEL', color='red', s=100)

660

661

ax2.set_title('Cross-Section: Hydraulic Conductivity', fontsize=14, fontweight='bold')

662

ax2.set_xlabel('Distance along section (m)', fontsize=12)

663

ax2.set_ylabel('Elevation (m)', fontsize=12)

664

665

plt.tight_layout()

666

plt.show()

667

```

668

669

### Particle Tracking Visualization

670

671

```python

672

import flopy

673

import flopy.utils as fpu

674

import matplotlib.pyplot as plt

675

import numpy as np

676

677

# Load model

678

mf = flopy.modflow.Modflow.load('model.nam')

679

680

# Create map view

681

fig, ax = plt.subplots(figsize=(14, 10))

682

mapview = flopy.plot.PlotMapView(model=mf, ax=ax, layer=0)

683

684

# Plot base head contours

685

try:

686

hds = fpu.HeadFile('model.hds')

687

head = hds.get_data(kstpkper=(0, 0))

688

689

contours = mapview.contour_array(

690

head,

691

levels=15,

692

colors='lightgray',

693

linewidths=1,

694

alpha=0.7

695

)

696

hds.close()

697

except:

698

pass

699

700

# Plot model grid (light)

701

mapview.plot_grid(lw=0.3, color='lightgray', alpha=0.5)

702

703

# Plot MODPATH results

704

try:

705

# Read pathline data

706

pth_file = fpu.PathlineFile('model.mppth')

707

pathlines = pth_file.get_alldata()

708

709

# Plot pathlines colored by travel time

710

pathline_plot = mapview.plot_pathline(

711

pathlines,

712

travel_time=None, # Will use time from pathline data

713

lw=2,

714

alpha=0.8,

715

cmap='viridis'

716

)

717

718

print(f"Plotted {len(pathlines)} pathlines")

719

720

# Read endpoint data

721

ep_file = fpu.EndpointFile('model.mpend')

722

endpoints = ep_file.get_alldata()

723

724

# Plot starting points (green) and ending points (red)

725

starting_points = mapview.plot_endpoint(

726

endpoints,

727

direction='starting',

728

marker='o',

729

color='green',

730

s=50,

731

label='Starting points',

732

alpha=0.8

733

)

734

735

ending_points = mapview.plot_endpoint(

736

endpoints,

737

direction='ending',

738

marker='s',

739

color='red',

740

s=30,

741

label='Ending points',

742

alpha=0.8

743

)

744

745

print(f"Plotted {len(endpoints)} particle endpoints")

746

747

except FileNotFoundError as e:

748

print(f"MODPATH files not found: {e}")

749

750

# Plot boundary conditions

751

mapview.plot_bc('WEL', color='blue', marker='o', s=150,

752

edgecolor='darkblue', linewidth=2, label='Wells')

753

mapview.plot_bc('RIV', color='cyan', lw=5, alpha=0.7, label='Rivers')

754

755

# Add colorbar for pathlines if they were plotted

756

try:

757

if 'pathline_plot' in locals():

758

cb = plt.colorbar(pathline_plot, ax=ax, shrink=0.8)

759

cb.set_label('Travel Time (days)', fontsize=12)

760

except:

761

pass

762

763

# Legend

764

ax.legend(loc='upper right', fontsize=10)

765

766

ax.set_title('Particle Tracking Results', fontsize=16, fontweight='bold')

767

ax.set_xlabel('X Coordinate (m)', fontsize=12)

768

ax.set_ylabel('Y Coordinate (m)', fontsize=12)

769

ax.set_aspect('equal')

770

771

plt.tight_layout()

772

plt.show()

773

```

774

775

### Multi-Layer Visualization

776

777

```python

778

import flopy

779

import flopy.utils as fpu

780

import matplotlib.pyplot as plt

781

import numpy as np

782

783

# Load model

784

mf = flopy.modflow.Modflow.load('model.nam')

785

nlay = mf.dis.nlay

786

787

# Create multi-panel figure

788

fig, axes = plt.subplots(2, 2, figsize=(16, 12))

789

axes = axes.flatten()

790

791

# Read head data

792

try:

793

hds = fpu.HeadFile('model.hds')

794

head = hds.get_data(kstpkper=(0, 0))

795

hds.close()

796

797

# Global head range for consistent coloring

798

vmin, vmax = head.min(), head.max()

799

levels = np.linspace(vmin, vmax, 20)

800

801

except FileNotFoundError:

802

head = None

803

vmin = vmax = None

804

levels = None

805

806

# Plot each layer

807

for i in range(min(nlay, 4)): # Up to 4 layers

808

ax = axes[i]

809

mapview = flopy.plot.PlotMapView(model=mf, ax=ax, layer=i)

810

811

# Plot grid

812

mapview.plot_grid(lw=0.3, color='black', alpha=0.5)

813

814

# Plot head data if available

815

if head is not None:

816

head_plot = mapview.plot_array(

817

head,

818

cmap='RdYlBu',

819

vmin=vmin,

820

vmax=vmax,

821

alpha=0.8

822

)

823

824

# Contour lines

825

contours = mapview.contour_array(

826

head,

827

levels=levels[::2], # Every other contour

828

colors='black',

829

linewidths=0.8,

830

alpha=0.7

831

)

832

833

# Plot boundary conditions

834

mapview.plot_bc('WEL', color='red', s=30, alpha=0.8)

835

mapview.plot_bc('RIV', color='blue', lw=2, alpha=0.7)

836

837

# Plot inactive cells

838

if hasattr(mf, 'bas6'):

839

mapview.plot_inactive(

840

ibound=mf.bas6.ibound.array,

841

color_noflow='lightgray'

842

)

843

844

ax.set_title(f'Layer {i+1}', fontsize=12, fontweight='bold')

845

ax.set_xlabel('X (m)', fontsize=10)

846

ax.set_ylabel('Y (m)', fontsize=10)

847

ax.set_aspect('equal')

848

849

# Add shared colorbar

850

if head is not None and 'head_plot' in locals():

851

# Create colorbar axis

852

cbar_ax = fig.add_axes([0.92, 0.15, 0.02, 0.7])

853

cb = plt.colorbar(head_plot, cax=cbar_ax)

854

cb.set_label('Head Elevation (m)', fontsize=12, rotation=270, labelpad=20)

855

856

fig.suptitle('Multi-Layer Head Distribution', fontsize=16, fontweight='bold')

857

plt.tight_layout()

858

plt.subplots_adjust(right=0.9)

859

plt.show()

860

```

861

862

### Advanced Concentration Plume Visualization

863

864

```python

865

import flopy

866

import flopy.utils as fpu

867

import matplotlib.pyplot as plt

868

import numpy as np

869

from matplotlib.colors import LogNorm

870

871

# Load MT3DMS results

872

try:

873

ucn = fpu.UcnFile('MT3D001.UCN')

874

times = ucn.get_times()

875

876

# Create time series plot

877

fig = plt.figure(figsize=(16, 12))

878

879

# Select representative times

880

time_indices = [0, len(times)//4, len(times)//2, -1]

881

882

for idx, time_idx in enumerate(time_indices):

883

ax = plt.subplot(2, 2, idx + 1)

884

885

# Get concentration data

886

conc = ucn.get_data(idx=time_idx)

887

time_val = times[time_idx]

888

889

# Create map view (assuming structured grid)

890

# Note: Would need model object for proper grid, using generic approach

891

892

# Plot concentration with log scale for better visualization

893

conc_masked = np.ma.masked_where(conc <= 0, conc)

894

895

im = ax.imshow(

896

conc_masked[0, :, :], # Top layer

897

cmap='plasma',

898

norm=LogNorm(vmin=0.01, vmax=conc.max()),

899

origin='lower',

900

alpha=0.8

901

)

902

903

# Add contour lines

904

contour_levels = np.logspace(-2, np.log10(conc.max()), 8)

905

cs = ax.contour(

906

conc[0, :, :],

907

levels=contour_levels,

908

colors='black',

909

linewidths=1,

910

alpha=0.6

911

)

912

ax.clabel(cs, inline=True, fontsize=8, fmt='%.2f')

913

914

ax.set_title(f'Time = {time_val:.1f} days', fontsize=12, fontweight='bold')

915

ax.set_xlabel('Column', fontsize=10)

916

ax.set_ylabel('Row', fontsize=10)

917

ax.set_aspect('equal')

918

919

# Add shared colorbar

920

cbar_ax = fig.add_axes([0.92, 0.15, 0.02, 0.7])

921

cb = plt.colorbar(im, cax=cbar_ax)

922

cb.set_label('Concentration (mg/L)', fontsize=12, rotation=270, labelpad=20)

923

924

fig.suptitle('Contaminant Plume Evolution', fontsize=16, fontweight='bold')

925

plt.tight_layout()

926

plt.subplots_adjust(right=0.9)

927

plt.show()

928

929

ucn.close()

930

931

except FileNotFoundError:

932

print("MT3DMS concentration file not found")

933

934

# Concentration breakthrough curves

935

try:

936

ucn = fpu.UcnFile('MT3D001.UCN')

937

938

# Define monitoring points

939

monitor_points = [(0, 10, 25), (0, 20, 50), (0, 30, 75)]

940

941

# Get time series

942

conc_ts = ucn.get_ts(idx=monitor_points)

943

times = ucn.get_times()

944

945

# Plot breakthrough curves

946

plt.figure(figsize=(12, 8))

947

948

for i, (lay, row, col) in enumerate(monitor_points):

949

plt.semilogy(

950

times,

951

conc_ts[:, i],

952

'o-',

953

linewidth=2,

954

markersize=4,

955

label=f'MW-{i+1} (R{row}, C{col})'

956

)

957

958

plt.xlabel('Time (days)', fontsize=12)

959

plt.ylabel('Concentration (mg/L)', fontsize=12)

960

plt.title('Concentration Breakthrough Curves', fontsize=14, fontweight='bold')

961

plt.legend(fontsize=10)

962

plt.grid(True, alpha=0.3)

963

plt.tight_layout()

964

plt.show()

965

966

ucn.close()

967

968

except FileNotFoundError:

969

print("MT3DMS concentration file not found")

970

```

971

972

### Custom Styling and Publication-Quality Plots

973

974

```python

975

import flopy

976

import matplotlib.pyplot as plt

977

import matplotlib as mpl

978

import numpy as np

979

980

# Set publication style

981

plt.style.use('seaborn-v0_8-paper') # Clean, publication-ready style

982

mpl.rcParams['font.size'] = 11

983

mpl.rcParams['axes.labelsize'] = 12

984

mpl.rcParams['axes.titlesize'] = 14

985

mpl.rcParams['xtick.labelsize'] = 10

986

mpl.rcParams['ytick.labelsize'] = 10

987

mpl.rcParams['legend.fontsize'] = 10

988

mpl.rcParams['figure.titlesize'] = 16

989

990

# Custom color schemes

991

custom_colors = {

992

'wells': '#d62728', # Red

993

'rivers': '#1f77b4', # Blue

994

'ghb': '#ff7f0e', # Orange

995

'drn': '#2ca02c', # Green

996

'inactive': '#7f7f7f', # Gray

997

'grid': '#cccccc' # Light gray

998

}

999

1000

# Load model

1001

mf = flopy.modflow.Modflow.load('model.nam')

1002

1003

# Create publication figure

1004

fig, ax = plt.subplots(figsize=(10, 8), dpi=300) # High DPI for publication

1005

1006

# Create map view

1007

mapview = flopy.plot.PlotMapView(model=mf, ax=ax, layer=0)

1008

1009

# Plot with custom styling

1010

mapview.plot_grid(

1011

lw=0.5,

1012

color=custom_colors['grid'],

1013

alpha=0.7

1014

)

1015

1016

# Plot boundary conditions with custom styling

1017

mapview.plot_bc(

1018

'WEL',

1019

color=custom_colors['wells'],

1020

marker='o',

1021

s=80,

1022

edgecolor='darkred',

1023

linewidth=1.5,

1024

label='Pumping Wells',

1025

zorder=5

1026

)

1027

1028

mapview.plot_bc(

1029

'RIV',

1030

color=custom_colors['rivers'],

1031

lw=3,

1032

alpha=0.8,

1033

label='Rivers',

1034

zorder=4

1035

)

1036

1037

mapview.plot_bc(

1038

'GHB',

1039

color=custom_colors['ghb'],

1040

lw=2,

1041

alpha=0.8,

1042

label='General Head Boundary',

1043

zorder=3

1044

)

1045

1046

# Plot head data with custom colormap

1047

try:

1048

hds = fpu.HeadFile('model.hds')

1049

head = hds.get_data(kstpkper=(0, 0))

1050

1051

# Use a perceptually uniform colormap

1052

head_plot = mapview.plot_array(

1053

head,

1054

cmap='viridis',

1055

alpha=0.7,

1056

zorder=1

1057

)

1058

1059

# Clean contours

1060

contours = mapview.contour_array(

1061

head,

1062

levels=np.linspace(head.min(), head.max(), 15),

1063

colors='white',

1064

linewidths=0.8,

1065

alpha=0.9,

1066

zorder=2

1067

)

1068

1069

# Format colorbar

1070

cb = plt.colorbar(

1071

head_plot,

1072

ax=ax,

1073

shrink=0.8,

1074

aspect=20,

1075

pad=0.02

1076

)

1077

cb.set_label('Hydraulic Head (m above MSL)', fontsize=12, labelpad=15)

1078

cb.ax.tick_params(labelsize=10)

1079

1080

hds.close()

1081

1082

except FileNotFoundError:

1083

pass

1084

1085

# Plot inactive cells

1086

if hasattr(mf, 'bas6'):

1087

mapview.plot_inactive(

1088

ibound=mf.bas6.ibound.array,

1089

color_noflow=custom_colors['inactive'],

1090

alpha=0.3

1091

)

1092

1093

# Professional formatting

1094

ax.set_xlabel('Easting (m)', fontsize=12, fontweight='bold')

1095

ax.set_ylabel('Northing (m)', fontsize=12, fontweight='bold')

1096

ax.set_title('Groundwater Flow Model - Steady State Heads',

1097

fontsize=14, fontweight='bold', pad=20)

1098

1099

# Clean legend

1100

legend = ax.legend(

1101

loc='upper left',

1102

frameon=True,

1103

fancybox=True,

1104

shadow=True,

1105

fontsize=10

1106

)

1107

legend.get_frame().set_facecolor('white')

1108

legend.get_frame().set_alpha(0.9)

1109

1110

# Set aspect ratio and tight layout

1111

ax.set_aspect('equal')

1112

1113

# Add north arrow and scale bar (custom functions would be needed)

1114

# add_north_arrow(ax, xy=(0.9, 0.9), size=0.05)

1115

# add_scale_bar(ax, length=1000, location='lower right')

1116

1117

# Grid and ticks

1118

ax.grid(True, alpha=0.3, linestyle='--', linewidth=0.5)

1119

ax.tick_params(axis='both', which='major', labelsize=10)

1120

1121

# Tight layout with padding

1122

plt.tight_layout(pad=1.5)

1123

1124

# Save in multiple formats for publication

1125

plt.savefig('model_heads.png', dpi=300, bbox_inches='tight',

1126

facecolor='white', edgecolor='none')

1127

plt.savefig('model_heads.pdf', bbox_inches='tight',

1128

facecolor='white', edgecolor='none')

1129

plt.savefig('model_heads.svg', bbox_inches='tight',

1130

facecolor='white', edgecolor='none')

1131

1132

plt.show()

1133

```

1134

1135

## Common Types

1136

1137

```python { .api }

1138

# Plotting types

1139

PlotArray = np.ndarray # 2D or 3D array for plotting

1140

ColorMap = Union[str, mpl.colors.Colormap]

1141

PlotExtent = tuple[float, float, float, float] # (xmin, xmax, ymin, ymax)

1142

1143

# Line and geometry types

1144

CrossSectionLine = dict[str, list[tuple[float, float]]]

1145

PlotLine = dict[str, Union[list, np.ndarray]]

1146

Coordinates = tuple[float, float]

1147

1148

# Style and formatting types

1149

PlotStyle = dict[str, Union[str, int, float]]

1150

ColorSpec = Union[str, tuple[float, float, float]]

1151

MarkerStyle = str

1152

LineStyle = str

1153

1154

# Vector data types

1155

VectorField = tuple[np.ndarray, np.ndarray] # (vx, vy)

1156

FlowData = tuple[np.ndarray, np.ndarray, np.ndarray] # (frf, fff, flf)

1157

DischargeData = tuple[np.ndarray, np.ndarray, np.ndarray] # (qx, qy, qz)

1158

1159

# Particle tracking types

1160

PathlineData = list[np.ndarray]

1161

EndpointData = np.ndarray

1162

TimeseriesData = np.ndarray

1163

1164

# 3D plotting types

1165

SurfaceData = np.ndarray # 2D array for surface plots

1166

VoxelData = np.ndarray # 3D boolean array for voxel plots

1167

ScatterData = tuple[np.ndarray, np.ndarray, np.ndarray] # (x, y, z)

1168

1169

# File and output types

1170

ImageFormat = Literal['png', 'pdf', 'svg', 'eps', 'jpg', 'tiff']

1171

PlotSize = tuple[float, float] # (width, height) in inches

1172

DPI = int # Dots per inch for raster output

1173

```

1174

1175

This comprehensive documentation covers the complete plotting and visualization API for FloPy including map views, cross-sections, 3D plots, and specialized visualizations. The examples demonstrate basic to advanced plotting scenarios including publication-quality figures, multi-layer visualization, and particle tracking results.