Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 89 additions & 47 deletions plots/contour-basic/implementations/python/altair.py
Original file line number Diff line number Diff line change
@@ -1,104 +1,146 @@
""" pyplots.ai
""" anyplot.ai
contour-basic: Basic Contour Plot
Library: altair 6.0.0 | Python 3.13.11
Quality: 98/100 | Created: 2025-12-23
Library: altair 6.1.0 | Python 3.14.4
Quality: 86/100 | Updated: 2026-04-24
"""

import altair as alt
import numpy as np
import pandas as pd
import importlib
import os
import sys


# Data - 2D Gaussian function on meshgrid
np.random.seed(42)
x = np.linspace(-3, 3, 80)
y = np.linspace(-3, 3, 80)
# Drop script directory from sys.path so the `altair` package resolves, not this file
sys.path[:] = [p for p in sys.path if os.path.abspath(p or ".") != os.path.dirname(os.path.abspath(__file__))]
alt = importlib.import_module("altair")
np = importlib.import_module("numpy")
pd = importlib.import_module("pandas")


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"

# Data — simulated topographic elevation map of a 10km x 10km mountain region
x = np.linspace(0, 10, 80)
y = np.linspace(0, 10, 80)
X, Y = np.meshgrid(x, y)

# Two overlapping Gaussian peaks for interesting contour patterns
Z = np.exp(-((X - 1) ** 2 + (Y - 1) ** 2)) + 0.8 * np.exp(-((X + 1) ** 2 + (Y + 0.5) ** 2))
elevation = (
850 * np.exp(-((X - 7) ** 2 + (Y - 7) ** 2) / 4.0)
+ 550 * np.exp(-((X - 2.5) ** 2 + (Y - 3) ** 2) / 3.0)
- 180 * np.exp(-((X - 5) ** 2 + (Y - 5) ** 2) / 8.0)
+ 12 * X
+ 350
)

# Flatten to DataFrame for filled contour background
df_fill = pd.DataFrame({"x": X.ravel(), "y": Y.ravel(), "z": Z.ravel()})
df_fill = pd.DataFrame({"x": X.ravel(), "y": Y.ravel(), "elevation": elevation.ravel()})

# Extract contour line segments using marching squares algorithm
levels = np.linspace(0.1, 1.6, 10)
# Contour line segments via marching squares
levels = np.arange(400, 1251, 100)
segments = []

for level in levels:
for i in range(len(y) - 1):
for j in range(len(x) - 1):
# Get 4 corners of cell
z00, z10, z01, z11 = Z[i, j], Z[i + 1, j], Z[i, j + 1], Z[i + 1, j + 1]
z00, z10, z01, z11 = elevation[i, j], elevation[i + 1, j], elevation[i, j + 1], elevation[i + 1, j + 1]
x0, x1, y0, y1 = x[j], x[j + 1], y[i], y[i + 1]

# Calculate which corners are above/below level (binary case)
case = int(z00 >= level) | (int(z10 >= level) << 1) | (int(z01 >= level) << 2) | (int(z11 >= level) << 3)

if case == 0 or case == 15:
continue

# Find edge crossings via linear interpolation
edges = []
if (case & 1) != (case >> 1) & 1: # Bottom edge (z00 to z10)
if (case & 1) != (case >> 1) & 1:
t = (level - z00) / (z10 - z00) if z10 != z00 else 0.5
edges.append((x0, y0 + t * (y1 - y0)))
if (case >> 1) & 1 != (case >> 3) & 1: # Right edge (z10 to z11)
if (case >> 1) & 1 != (case >> 3) & 1:
t = (level - z10) / (z11 - z10) if z11 != z10 else 0.5
edges.append((x0 + t * (x1 - x0), y1))
if (case >> 2) & 1 != (case >> 3) & 1: # Top edge (z01 to z11)
if (case >> 2) & 1 != (case >> 3) & 1:
t = (level - z01) / (z11 - z01) if z11 != z01 else 0.5
edges.append((x1, y0 + t * (y1 - y0)))
if (case & 1) != (case >> 2) & 1: # Left edge (z00 to z01)
if (case & 1) != (case >> 2) & 1:
t = (level - z00) / (z01 - z00) if z01 != z00 else 0.5
edges.append((x0 + t * (x1 - x0), y0))

# Connect edge crossings as line segments
if len(edges) >= 2:
segments.append(
{"x1": edges[0][0], "y1": edges[0][1], "x2": edges[1][0], "y2": edges[1][1], "level": level}
{"x1": edges[0][0], "y1": edges[0][1], "x2": edges[1][0], "y2": edges[1][1], "level": float(level)}
)
if len(edges) == 4: # Saddle point case
if len(edges) == 4:
segments.append(
{"x1": edges[2][0], "y1": edges[2][1], "x2": edges[3][0], "y2": edges[3][1], "level": level}
{
"x1": edges[2][0],
"y1": edges[2][1],
"x2": edges[3][0],
"y2": edges[3][1],
"level": float(level),
}
)

df_lines = pd.DataFrame(segments)

# Filled contour background using rect marks
# Plot — filled contour background
filled = (
alt.Chart(df_fill)
.mark_rect()
.encode(
x=alt.X("x:Q", bin=alt.Bin(maxbins=80), title="X Value"),
y=alt.Y("y:Q", bin=alt.Bin(maxbins=80), title="Y Value"),
x=alt.X("x:Q", bin=alt.Bin(maxbins=80), title="Distance East (km)"),
y=alt.Y("y:Q", bin=alt.Bin(maxbins=80), title="Distance North (km)"),
color=alt.Color(
"mean(z):Q",
"mean(elevation):Q",
scale=alt.Scale(scheme="viridis"),
title="Z Value",
legend=alt.Legend(titleFontSize=20, labelFontSize=18, gradientLength=500, gradientThickness=30),
title="Elevation (m)",
legend=alt.Legend(titleFontSize=22, labelFontSize=18, gradientLength=600, gradientThickness=28),
),
)
)

# Contour lines overlaid with contrasting white color for visibility
# Thin contour lines (all levels)
lines = (
alt.Chart(df_lines)
.mark_rule(strokeWidth=3, opacity=1.0, color="white")
.mark_rule(strokeWidth=1.2, opacity=0.35, color="white")
.encode(x="x1:Q", y="y1:Q", x2="x2:Q", y2="y2:Q")
)

# Combine filled regions and contour lines (lines layered on top)
chart = (
(filled + lines)
.properties(width=1420, height=785, title="contour-basic · altair · pyplots.ai")
.configure_title(fontSize=32, anchor="middle")
.configure_axis(labelFontSize=20, titleFontSize=24, tickSize=10)
.configure_view(strokeWidth=0)
# Emphasised contour lines every 200 m
major_mask = (df_lines["level"] % 200 == 0) if not df_lines.empty else pd.Series([], dtype=bool)
df_major = df_lines[major_mask].copy() if not df_lines.empty else df_lines

major_lines = (
alt.Chart(df_major)
.mark_rule(strokeWidth=2.2, opacity=0.95, color="white")
.encode(x="x1:Q", y="y1:Q", x2="x2:Q", y2="y2:Q")
)

# Save as PNG (scale_factor=3.0 for 4800x2700)
chart.save("plot.png", scale_factor=3.0)
chart = (
(filled + lines + major_lines)
.properties(
width=1420,
height=785,
title=alt.Title(
"Mountain Terrain · contour-basic · altair · anyplot.ai", fontSize=28, anchor="middle", color=INK
),
background=PAGE_BG,
)
.configure_view(fill=PAGE_BG, stroke=None)
.configure_axis(
domainColor=INK_SOFT,
tickColor=INK_SOFT,
gridColor=INK,
gridOpacity=0.10,
labelColor=INK_SOFT,
titleColor=INK,
labelFontSize=18,
titleFontSize=22,
tickSize=8,
)
.configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)
)

# Save interactive HTML
chart.save("plot.html")
chart.save(f"plot-{THEME}.png", scale_factor=3.0)
chart.save(f"plot-{THEME}.html")
Loading
Loading