Skip to content

Commit 9e8fc12

Browse files
feat: implement step-basic
- Added spec for basic step plot showing discrete changes - Implemented matplotlib version with step function - Implemented seaborn version with enhanced styling - Both implementations support all spec requirements: - Configurable step position (pre/post/mid) - Optional markers at data points - Handles both numeric and categorical x-axis - Full input validation and error handling - Type hints and comprehensive documentation Closes #38 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 5866763 commit 9e8fc12

3 files changed

Lines changed: 422 additions & 0 deletions

File tree

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
"""
2+
step-basic: Basic Step Plot
3+
Implementation for: matplotlib
4+
Variant: default
5+
Python: 3.10+
6+
"""
7+
8+
import matplotlib.pyplot as plt
9+
import pandas as pd
10+
import numpy as np
11+
from typing import TYPE_CHECKING
12+
13+
if TYPE_CHECKING:
14+
from matplotlib.figure import Figure
15+
16+
17+
def create_plot(
18+
data: pd.DataFrame,
19+
x: str,
20+
y: str,
21+
where: str = "pre",
22+
color: str = "steelblue",
23+
linewidth: float = 2.0,
24+
alpha: float = 0.9,
25+
linestyle: str = "-",
26+
marker: str | None = None,
27+
markersize: float = 6,
28+
title: str | None = None,
29+
xlabel: str | None = None,
30+
ylabel: str | None = None,
31+
figsize: tuple[float, float] = (10, 6),
32+
**kwargs
33+
) -> "Figure":
34+
"""
35+
Create a basic step plot showing discrete changes in values.
36+
37+
Step plots display data as a series of horizontal and vertical lines,
38+
showing discrete changes between values. Ideal for visualizing data that
39+
changes at specific intervals.
40+
41+
Args:
42+
data: Input DataFrame with required columns
43+
x: Column name for x-axis values (sequential or time-based)
44+
y: Column name for y-axis values (numeric levels)
45+
where: Position of steps - "pre", "post", or "mid" (default: "pre")
46+
color: Line color (default: "steelblue")
47+
linewidth: Line width (default: 2.0)
48+
alpha: Transparency level 0.0-1.0 (default: 0.9)
49+
linestyle: Line style - "-", "--", "-.", or ":" (default: "-")
50+
marker: Optional marker style for data points (default: None)
51+
markersize: Size of markers if used (default: 6)
52+
title: Optional plot title (default: None)
53+
xlabel: Custom x-axis label (default: column name)
54+
ylabel: Custom y-axis label (default: column name)
55+
figsize: Figure size in inches (default: (10, 6))
56+
**kwargs: Additional parameters passed to step function
57+
58+
Returns:
59+
Matplotlib Figure object
60+
61+
Raises:
62+
ValueError: If data is empty or 'where' parameter is invalid
63+
KeyError: If required columns not found
64+
65+
Example:
66+
>>> import pandas as pd
67+
>>> data = pd.DataFrame({
68+
... 'time': [1, 2, 3, 4, 5],
69+
... 'level': [10, 15, 12, 18, 14]
70+
... })
71+
>>> fig = create_plot(data, 'time', 'level')
72+
"""
73+
# Input validation
74+
if data.empty:
75+
raise ValueError("Data cannot be empty")
76+
77+
# Check required columns
78+
required_columns = [x, y]
79+
missing_columns = []
80+
for col in required_columns:
81+
if col not in data.columns:
82+
missing_columns.append(col)
83+
84+
if missing_columns:
85+
available = ", ".join(data.columns)
86+
missing = ", ".join(missing_columns)
87+
raise KeyError(f"Column(s) '{missing}' not found. Available columns: {available}")
88+
89+
# Validate 'where' parameter
90+
if where not in ["pre", "post", "mid"]:
91+
raise ValueError(f"'where' parameter must be 'pre', 'post', or 'mid', got '{where}'")
92+
93+
# Create figure
94+
fig, ax = plt.subplots(figsize=figsize)
95+
96+
# Sort data by x values to ensure proper step plot
97+
plot_data = data[[x, y]].copy()
98+
plot_data = plot_data.sort_values(by=x)
99+
100+
# Plot step lines
101+
ax.step(plot_data[x], plot_data[y],
102+
where=where,
103+
color=color,
104+
linewidth=linewidth,
105+
alpha=alpha,
106+
linestyle=linestyle,
107+
**kwargs)
108+
109+
# Add markers if specified
110+
if marker:
111+
ax.plot(plot_data[x], plot_data[y],
112+
marker=marker,
113+
markersize=markersize,
114+
color=color,
115+
alpha=alpha,
116+
linestyle='None') # Only markers, no line
117+
118+
# Apply styling
119+
ax.set_xlabel(xlabel or x, fontsize=12)
120+
ax.set_ylabel(ylabel or y, fontsize=12)
121+
122+
# Add title if provided
123+
if title:
124+
ax.set_title(title, fontsize=14, fontweight='bold')
125+
126+
# Add subtle grid
127+
ax.grid(True, alpha=0.3, linestyle='-', linewidth=0.5)
128+
129+
# Ensure no overlapping labels
130+
fig.autofmt_xdate() # Automatically format date labels if x is datetime
131+
132+
# Layout
133+
plt.tight_layout()
134+
135+
return fig
136+
137+
138+
if __name__ == '__main__':
139+
# Sample data for testing
140+
np.random.seed(42) # For reproducibility
141+
142+
# Create sample data representing interest rate changes over time
143+
data = pd.DataFrame({
144+
'month': range(1, 13),
145+
'rate': [2.0, 2.0, 2.25, 2.25, 2.25, 2.5, 2.5, 2.75, 2.75, 2.75, 3.0, 3.0]
146+
})
147+
148+
# Create plot
149+
fig = create_plot(
150+
data,
151+
x='month',
152+
y='rate',
153+
title='Interest Rate Changes Over Time',
154+
xlabel='Month',
155+
ylabel='Interest Rate (%)',
156+
where='post', # Step occurs after the data point
157+
marker='o', # Show actual data points
158+
markersize=4
159+
)
160+
161+
# Save for inspection - ALWAYS use 'plot.png' as filename
162+
plt.savefig('plot.png', dpi=300, bbox_inches='tight')
163+
print("Plot saved to plot.png")
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""
2+
step-basic: Basic Step Plot
3+
Implementation for: seaborn
4+
Variant: default
5+
Python: 3.10+
6+
"""
7+
8+
import seaborn as sns
9+
import matplotlib.pyplot as plt
10+
import pandas as pd
11+
import numpy as np
12+
from typing import TYPE_CHECKING
13+
14+
if TYPE_CHECKING:
15+
from matplotlib.figure import Figure
16+
17+
18+
def create_plot(
19+
data: pd.DataFrame,
20+
x: str,
21+
y: str,
22+
where: str = "pre",
23+
color: str = "steelblue",
24+
linewidth: float = 2.0,
25+
alpha: float = 0.9,
26+
linestyle: str = "-",
27+
marker: str | None = None,
28+
markersize: float = 6,
29+
title: str | None = None,
30+
xlabel: str | None = None,
31+
ylabel: str | None = None,
32+
figsize: tuple[float, float] = (10, 6),
33+
**kwargs
34+
) -> "Figure":
35+
"""
36+
Create a basic step plot showing discrete changes in values using seaborn styling.
37+
38+
Step plots display data as a series of horizontal and vertical lines,
39+
showing discrete changes between values. This implementation uses matplotlib's
40+
step function with seaborn's aesthetic styling for better visual appeal.
41+
42+
Args:
43+
data: Input DataFrame with required columns
44+
x: Column name for x-axis values (sequential or time-based)
45+
y: Column name for y-axis values (numeric levels)
46+
where: Position of steps - "pre", "post", or "mid" (default: "pre")
47+
color: Line color (default: "steelblue")
48+
linewidth: Line width (default: 2.0)
49+
alpha: Transparency level 0.0-1.0 (default: 0.9)
50+
linestyle: Line style - "-", "--", "-.", or ":" (default: "-")
51+
marker: Optional marker style for data points (default: None)
52+
markersize: Size of markers if used (default: 6)
53+
title: Optional plot title (default: None)
54+
xlabel: Custom x-axis label (default: column name)
55+
ylabel: Custom y-axis label (default: column name)
56+
figsize: Figure size in inches (default: (10, 6))
57+
**kwargs: Additional parameters passed to step function
58+
59+
Returns:
60+
Matplotlib Figure object with seaborn styling
61+
62+
Raises:
63+
ValueError: If data is empty or 'where' parameter is invalid
64+
KeyError: If required columns not found
65+
66+
Example:
67+
>>> import pandas as pd
68+
>>> data = pd.DataFrame({
69+
... 'quarter': ['Q1', 'Q2', 'Q3', 'Q4'],
70+
... 'revenue': [100, 120, 115, 140]
71+
... })
72+
>>> fig = create_plot(data, 'quarter', 'revenue')
73+
"""
74+
# Input validation
75+
if data.empty:
76+
raise ValueError("Data cannot be empty")
77+
78+
# Check required columns
79+
required_columns = [x, y]
80+
missing_columns = []
81+
for col in required_columns:
82+
if col not in data.columns:
83+
missing_columns.append(col)
84+
85+
if missing_columns:
86+
available = ", ".join(data.columns)
87+
missing = ", ".join(missing_columns)
88+
raise KeyError(f"Column(s) '{missing}' not found. Available columns: {available}")
89+
90+
# Validate 'where' parameter
91+
if where not in ["pre", "post", "mid"]:
92+
raise ValueError(f"'where' parameter must be 'pre', 'post', or 'mid', got '{where}'")
93+
94+
# Set seaborn style
95+
sns.set_style("whitegrid")
96+
97+
# Create figure with seaborn context
98+
with sns.plotting_context("notebook", font_scale=1.1):
99+
fig, ax = plt.subplots(figsize=figsize)
100+
101+
# Prepare data - ensure it's sorted
102+
plot_data = data[[x, y]].copy()
103+
104+
# Handle categorical x-axis by converting to numeric positions
105+
if plot_data[x].dtype == 'object' or pd.api.types.is_categorical_dtype(plot_data[x]):
106+
x_labels = plot_data[x].unique()
107+
x_positions = np.arange(len(plot_data[x]))
108+
plot_x = x_positions
109+
use_categorical = True
110+
else:
111+
plot_data = plot_data.sort_values(by=x)
112+
plot_x = plot_data[x]
113+
use_categorical = False
114+
115+
# Plot step lines
116+
ax.step(plot_x, plot_data[y],
117+
where=where,
118+
color=color,
119+
linewidth=linewidth,
120+
alpha=alpha,
121+
linestyle=linestyle,
122+
**kwargs)
123+
124+
# Add markers if specified
125+
if marker:
126+
ax.plot(plot_x, plot_data[y],
127+
marker=marker,
128+
markersize=markersize,
129+
color=color,
130+
alpha=alpha,
131+
linestyle='None') # Only markers, no line
132+
133+
# Handle categorical x-axis labels
134+
if use_categorical:
135+
ax.set_xticks(x_positions)
136+
ax.set_xticklabels(x_labels)
137+
138+
# Apply labels with seaborn styling
139+
ax.set_xlabel(xlabel or x, fontsize=12, fontweight='medium')
140+
ax.set_ylabel(ylabel or y, fontsize=12, fontweight='medium')
141+
142+
# Add title if provided
143+
if title:
144+
ax.set_title(title, fontsize=14, fontweight='bold', pad=20)
145+
146+
# Customize grid for better visibility
147+
ax.grid(True, alpha=0.3, linestyle='-', linewidth=0.5)
148+
ax.set_axisbelow(True) # Grid behind data
149+
150+
# Remove top and right spines for cleaner look
151+
sns.despine()
152+
153+
# Ensure no overlapping labels
154+
fig.autofmt_xdate() # Automatically format date labels if x is datetime
155+
156+
# Adjust layout
157+
plt.tight_layout()
158+
159+
# Reset seaborn defaults to avoid affecting other plots
160+
sns.reset_defaults()
161+
162+
return fig
163+
164+
165+
if __name__ == '__main__':
166+
# Sample data for testing
167+
np.random.seed(42) # For reproducibility
168+
169+
# Create sample data representing quarterly sales performance
170+
data = pd.DataFrame({
171+
'quarter': ['Q1 2023', 'Q2 2023', 'Q3 2023', 'Q4 2023',
172+
'Q1 2024', 'Q2 2024', 'Q3 2024', 'Q4 2024'],
173+
'sales': [45, 48, 48, 52, 55, 55, 58, 62]
174+
})
175+
176+
# Create plot with seaborn styling
177+
fig = create_plot(
178+
data,
179+
x='quarter',
180+
y='sales',
181+
title='Quarterly Sales Performance',
182+
xlabel='Quarter',
183+
ylabel='Sales (in millions)',
184+
where='post', # Step occurs after the data point
185+
color='#2E86AB', # Nice blue color
186+
marker='o', # Show actual data points
187+
markersize=5
188+
)
189+
190+
# Save for inspection - ALWAYS use 'plot.png' as filename
191+
plt.savefig('plot.png', dpi=300, bbox_inches='tight')
192+
print("Plot saved to plot.png")

0 commit comments

Comments
 (0)