Skip to content

Commit 10ae0c7

Browse files
Add proper growth chart visualization
- Created generate_growth_chart.py script with matplotlib - Replaced emoji sparkline with actual time-series chart - Chart shows dates (X-axis) vs prompt count (Y-axis) - Updated README stats script to use proper chart - Added markers to protect manual README edits from automation - Chart saved as images/growth_chart.png
1 parent 9d13c9d commit 10ae0c7

File tree

5 files changed

+204
-17
lines changed

5 files changed

+204
-17
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ Thumbs.db
1717
# Temporary files
1818
*.tmp
1919
*.bak
20+
.chart-env

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66

77
![alt text](images/banner.webp)
88

9-
9+
<!-- BEGIN_STATS_SECTION -->
1010
## 📊 Library Statistics
1111

12-
📈 ▁█▅▅
12+
![Growth Chart](images/growth_chart.png)
1313

1414
**Total System Prompts:** 922 | **Last Updated:** 2025-08-27
1515

1616
*This library has grown from 892 to 922 prompts since tracking began*
17+
<!-- END_STATS_SECTION -->
1718

1819
## Table of Contents
1920

images/growth_chart.png

224 KB
Loading

scripts/generate_growth_chart.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Generate a proper time-series chart showing System Prompt Library growth over time.
4+
5+
Creates a line chart with:
6+
- X-axis: Time (dates)
7+
- Y-axis: Number of system prompts
8+
- Saves as PNG image for embedding in README
9+
"""
10+
11+
import json
12+
import matplotlib.pyplot as plt
13+
import matplotlib.dates as mdates
14+
from datetime import datetime
15+
from pathlib import Path
16+
import pandas as pd
17+
18+
19+
def generate_growth_chart(repo_root: Path, output_file: str = "images/growth_chart.png"):
20+
"""Generate and save a growth chart from growth_history.json."""
21+
22+
growth_file = repo_root / "growth_history.json"
23+
output_path = repo_root / output_file
24+
25+
# Ensure output directory exists
26+
output_path.parent.mkdir(parents=True, exist_ok=True)
27+
28+
if not growth_file.exists():
29+
print("❌ growth_history.json not found")
30+
return False
31+
32+
# Load growth data
33+
with open(growth_file, 'r') as f:
34+
growth_data = json.load(f)
35+
36+
entries = growth_data.get("entries", [])
37+
if len(entries) < 2:
38+
print("❌ Need at least 2 data points to create a meaningful chart")
39+
return False
40+
41+
# Extract dates and counts
42+
dates = []
43+
counts = []
44+
45+
for entry in entries:
46+
# Try different date field names
47+
date_str = entry.get("date") or entry.get("updated") or entry.get("created")
48+
if date_str:
49+
# Parse date (handle both date-only and ISO timestamp formats)
50+
try:
51+
if 'T' in date_str:
52+
# ISO timestamp format
53+
date_obj = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
54+
else:
55+
# Date-only format
56+
date_obj = datetime.strptime(date_str, '%Y-%m-%d')
57+
58+
dates.append(date_obj)
59+
counts.append(entry["count"])
60+
except ValueError as e:
61+
print(f"⚠️ Skipping invalid date: {date_str} - {e}")
62+
continue
63+
64+
if len(dates) < 2:
65+
print("❌ Not enough valid date entries found")
66+
return False
67+
68+
# Create the chart
69+
plt.style.use('default')
70+
fig, ax = plt.subplots(figsize=(12, 6))
71+
72+
# Plot the line chart
73+
ax.plot(dates, counts, marker='o', linewidth=2.5, markersize=6,
74+
color='#2E86AB', markerfacecolor='#A23B72', markeredgecolor='white', markeredgewidth=1.5)
75+
76+
# Customize the chart
77+
ax.set_title('System Prompt Library Growth Over Time', fontsize=16, fontweight='bold', pad=20)
78+
ax.set_xlabel('Date', fontsize=12, fontweight='bold')
79+
ax.set_ylabel('Number of System Prompts', fontsize=12, fontweight='bold')
80+
81+
# Format dates on x-axis
82+
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
83+
ax.xaxis.set_major_locator(mdates.DayLocator(interval=max(1, len(dates)//6))) # Show ~6 date labels
84+
plt.xticks(rotation=45)
85+
86+
# Add grid
87+
ax.grid(True, alpha=0.3, linestyle='--')
88+
89+
# Set y-axis to start from a reasonable minimum
90+
min_count = min(counts)
91+
max_count = max(counts)
92+
padding = (max_count - min_count) * 0.1
93+
ax.set_ylim(min_count - padding, max_count + padding)
94+
95+
# Add value labels on data points
96+
for date, count in zip(dates, counts):
97+
ax.annotate(f'{count}', (date, count), textcoords="offset points",
98+
xytext=(0,10), ha='center', fontsize=9, fontweight='bold')
99+
100+
# Improve layout
101+
plt.tight_layout()
102+
103+
# Save the chart
104+
plt.savefig(output_path, dpi=300, bbox_inches='tight', facecolor='white', edgecolor='none')
105+
plt.close()
106+
107+
print(f"✅ Growth chart saved to {output_file}")
108+
print(f"📊 Chart shows {len(dates)} data points from {dates[0].strftime('%Y-%m-%d')} to {dates[-1].strftime('%Y-%m-%d')}")
109+
print(f"📈 Growth: {min_count}{max_count} prompts ({max_count - min_count:+d})")
110+
111+
return True
112+
113+
114+
def main():
115+
"""Main function."""
116+
repo_root = Path(__file__).parent.parent
117+
118+
print("📊 Generating System Prompt Library growth chart...")
119+
120+
# Check if matplotlib is available
121+
try:
122+
import matplotlib.pyplot as plt
123+
import matplotlib.dates as mdates
124+
except ImportError:
125+
print("❌ matplotlib is required. Install with: pip install matplotlib")
126+
return 1
127+
128+
success = generate_growth_chart(repo_root)
129+
130+
if success:
131+
print("✅ Growth chart generation completed successfully")
132+
else:
133+
print("❌ Failed to generate growth chart")
134+
return 1
135+
136+
return 0
137+
138+
139+
if __name__ == "__main__":
140+
exit(main())

scripts/update_readme_stats.py

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
import json
1010
import re
11+
import subprocess
12+
import sys
1113
from pathlib import Path
1214
from datetime import datetime
1315
from typing import List, Dict
@@ -37,6 +39,35 @@ def generate_sparkline(counts: List[int]) -> str:
3739
return sparkline
3840

3941

42+
def generate_growth_chart(repo_root: Path) -> bool:
43+
"""Generate growth chart using the chart generation script."""
44+
try:
45+
# Run the chart generation script in the virtual environment
46+
venv_python = repo_root / "chart-env" / "bin" / "python3"
47+
chart_script = repo_root / "scripts" / "generate_growth_chart.py"
48+
49+
if venv_python.exists() and chart_script.exists():
50+
result = subprocess.run(
51+
[str(venv_python), str(chart_script)],
52+
cwd=str(repo_root),
53+
capture_output=True,
54+
text=True
55+
)
56+
return result.returncode == 0
57+
else:
58+
# Fallback: try with system python
59+
result = subprocess.run(
60+
[sys.executable, str(chart_script)],
61+
cwd=str(repo_root),
62+
capture_output=True,
63+
text=True
64+
)
65+
return result.returncode == 0
66+
except Exception as e:
67+
print(f"⚠️ Chart generation failed: {e}")
68+
return False
69+
70+
4071
def load_growth_data(repo_root: Path) -> tuple:
4172
"""Load growth history and current metadata."""
4273
growth_file = repo_root / "growth_history.json"
@@ -80,18 +111,26 @@ def update_readme_with_stats(repo_root: Path):
80111
except:
81112
formatted_date = datetime.now().strftime('%Y-%m-%d')
82113

83-
# Generate sparkline from growth history
114+
# Generate growth chart
115+
chart_generated = generate_growth_chart(repo_root)
116+
117+
# Get counts for fallback text
84118
counts = [entry["count"] for entry in growth_data.get("entries", [])]
85119
if not counts:
86120
counts = [current_count]
87121

88-
sparkline = generate_sparkline(counts)
122+
# Create stats section with proper chart
123+
if chart_generated:
124+
chart_section = "![Growth Chart](images/growth_chart.png)"
125+
else:
126+
# Fallback to sparkline if chart generation fails
127+
chart_section = generate_sparkline(counts)
89128

90129
# Create stats section
91130
stats_section = f"""
92131
## 📊 Library Statistics
93132
94-
{sparkline}
133+
{chart_section}
95134
96135
**Total System Prompts:** {current_count:,} | **Last Updated:** {formatted_date}
97136
@@ -103,26 +142,31 @@ def update_readme_with_stats(repo_root: Path):
103142
with open(readme_file, 'r', encoding='utf-8') as f:
104143
content = f.read()
105144

106-
# Check if stats section already exists
107-
stats_pattern = r'## 📊 Library Statistics.*?(?=##|\Z)'
108-
109-
if re.search(stats_pattern, content, re.DOTALL):
110-
# Replace existing stats section
111-
new_content = re.sub(stats_pattern, stats_section.strip() + '\n\n', content, flags=re.DOTALL)
112-
print("✅ Updated existing statistics section")
145+
# Use markers to only update the statistics section, preserving manual edits
146+
stats_begin_marker = "<!-- BEGIN_STATS_SECTION -->"
147+
stats_end_marker = "<!-- END_STATS_SECTION -->"
148+
149+
# Check if markers exist
150+
if stats_begin_marker in content and stats_end_marker in content:
151+
# Replace only the content between markers
152+
pattern = f"{stats_begin_marker}.*?{stats_end_marker}"
153+
replacement = f"{stats_begin_marker}\n{stats_section.strip()}\n{stats_end_marker}"
154+
new_content = re.sub(pattern, replacement, content, flags=re.DOTALL)
155+
print("✅ Updated existing statistics section between markers")
113156
else:
114-
# Insert stats section after the badges but before Table of Contents
157+
# Insert stats section with markers after the badges but before Table of Contents
115158
# Look for the pattern: badges -> image -> Table of Contents
116159
toc_pattern = r'(!\[alt text\].*?\n\n)(## Table of Contents)'
117160

118161
if re.search(toc_pattern, content, re.DOTALL):
162+
stats_with_markers = f"{stats_begin_marker}\n{stats_section.strip()}\n{stats_end_marker}\n\n"
119163
new_content = re.sub(
120164
toc_pattern,
121-
r'\1' + stats_section + r'\2',
165+
r'\1' + stats_with_markers + r'\2',
122166
content,
123167
flags=re.DOTALL
124168
)
125-
print("✅ Added new statistics section before Table of Contents")
169+
print("✅ Added new statistics section with markers before Table of Contents")
126170
else:
127171
# Fallback: add after the first heading
128172
lines = content.split('\n')
@@ -132,9 +176,10 @@ def update_readme_with_stats(repo_root: Path):
132176
insert_pos = i
133177
break
134178

135-
lines.insert(insert_pos, stats_section.strip())
179+
stats_with_markers = f"{stats_begin_marker}\n{stats_section.strip()}\n{stats_end_marker}"
180+
lines.insert(insert_pos, stats_with_markers)
136181
new_content = '\n'.join(lines)
137-
print("✅ Added new statistics section (fallback method)")
182+
print("✅ Added new statistics section with markers (fallback method)")
138183

139184
# Write updated content
140185
with open(readme_file, 'w', encoding='utf-8') as f:

0 commit comments

Comments
 (0)