diff --git a/app/index.html b/app/index.html index 75fb64b57d..0ed5adc3a9 100644 --- a/app/index.html +++ b/app/index.html @@ -4,8 +4,25 @@ - - pyplots - AI-Powered Plotting Examples + + pyplots.ai + + + + + + + + + + + + + + + + + diff --git a/app/public/og-image.png b/app/public/og-image.png new file mode 100644 index 0000000000..964eecd1e4 Binary files /dev/null and b/app/public/og-image.png differ diff --git a/app/public/robots.txt b/app/public/robots.txt new file mode 100644 index 0000000000..0a50661f44 --- /dev/null +++ b/app/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://pyplots.ai/sitemap.xml diff --git a/app/public/sitemap.xml b/app/public/sitemap.xml new file mode 100644 index 0000000000..9d31fb2932 --- /dev/null +++ b/app/public/sitemap.xml @@ -0,0 +1,9 @@ + + + + https://pyplots.ai/ + 2025-12-07 + weekly + 1.0 + + diff --git a/plots/bokeh/scatter/scatter-color-groups/default.py b/plots/bokeh/scatter/scatter-color-groups/default.py index 5b0fe00573..77106ff43d 100644 --- a/plots/bokeh/scatter/scatter-color-groups/default.py +++ b/plots/bokeh/scatter/scatter-color-groups/default.py @@ -14,19 +14,25 @@ np.random.seed(42) n_per_group = 50 -data = pd.DataFrame({ - "sepal_length": np.concatenate([ - np.random.normal(5.0, 0.35, n_per_group), - np.random.normal(5.9, 0.50, n_per_group), - np.random.normal(6.6, 0.60, n_per_group), - ]), - "sepal_width": np.concatenate([ - np.random.normal(3.4, 0.38, n_per_group), - np.random.normal(2.8, 0.30, n_per_group), - np.random.normal(3.0, 0.30, n_per_group), - ]), - "species": ["setosa"] * n_per_group + ["versicolor"] * n_per_group + ["virginica"] * n_per_group, -}) +data = pd.DataFrame( + { + "sepal_length": np.concatenate( + [ + np.random.normal(5.0, 0.35, n_per_group), + np.random.normal(5.9, 0.50, n_per_group), + np.random.normal(6.6, 0.60, n_per_group), + ] + ), + "sepal_width": np.concatenate( + [ + np.random.normal(3.4, 0.38, n_per_group), + np.random.normal(2.8, 0.30, n_per_group), + np.random.normal(3.0, 0.30, n_per_group), + ] + ), + "species": ["setosa"] * n_per_group + ["versicolor"] * n_per_group + ["virginica"] * n_per_group, + } +) # Color palette (from style guide) colors = ["#306998", "#FFD43B", "#DC2626", "#059669", "#8B5CF6", "#F97316"] diff --git a/plots/matplotlib/scatter/scatter-color-groups/default.py b/plots/matplotlib/scatter/scatter-color-groups/default.py index 7f45e2c195..8270328253 100644 --- a/plots/matplotlib/scatter/scatter-color-groups/default.py +++ b/plots/matplotlib/scatter/scatter-color-groups/default.py @@ -12,19 +12,25 @@ np.random.seed(42) n_per_group = 50 -data = pd.DataFrame({ - "sepal_length": np.concatenate([ - np.random.normal(5.0, 0.35, n_per_group), - np.random.normal(5.9, 0.50, n_per_group), - np.random.normal(6.6, 0.60, n_per_group), - ]), - "sepal_width": np.concatenate([ - np.random.normal(3.4, 0.38, n_per_group), - np.random.normal(2.8, 0.30, n_per_group), - np.random.normal(3.0, 0.30, n_per_group), - ]), - "species": ["setosa"] * n_per_group + ["versicolor"] * n_per_group + ["virginica"] * n_per_group, -}) +data = pd.DataFrame( + { + "sepal_length": np.concatenate( + [ + np.random.normal(5.0, 0.35, n_per_group), + np.random.normal(5.9, 0.50, n_per_group), + np.random.normal(6.6, 0.60, n_per_group), + ] + ), + "sepal_width": np.concatenate( + [ + np.random.normal(3.4, 0.38, n_per_group), + np.random.normal(2.8, 0.30, n_per_group), + np.random.normal(3.0, 0.30, n_per_group), + ] + ), + "species": ["setosa"] * n_per_group + ["versicolor"] * n_per_group + ["virginica"] * n_per_group, + } +) # Color palette (colorblind safe from style guide) colors = ["#306998", "#FFD43B", "#DC2626"] diff --git a/plots/plotnine/point/scatter-color-groups/default.py b/plots/plotnine/point/scatter-color-groups/default.py index 5c63833376..367559f3c5 100644 --- a/plots/plotnine/point/scatter-color-groups/default.py +++ b/plots/plotnine/point/scatter-color-groups/default.py @@ -12,19 +12,25 @@ np.random.seed(42) n_per_group = 50 -data = pd.DataFrame({ - "sepal_length": np.concatenate([ - np.random.normal(5.0, 0.35, n_per_group), - np.random.normal(5.9, 0.50, n_per_group), - np.random.normal(6.6, 0.60, n_per_group), - ]), - "sepal_width": np.concatenate([ - np.random.normal(3.4, 0.38, n_per_group), - np.random.normal(2.8, 0.30, n_per_group), - np.random.normal(3.0, 0.30, n_per_group), - ]), - "species": ["setosa"] * n_per_group + ["versicolor"] * n_per_group + ["virginica"] * n_per_group, -}) +data = pd.DataFrame( + { + "sepal_length": np.concatenate( + [ + np.random.normal(5.0, 0.35, n_per_group), + np.random.normal(5.9, 0.50, n_per_group), + np.random.normal(6.6, 0.60, n_per_group), + ] + ), + "sepal_width": np.concatenate( + [ + np.random.normal(3.4, 0.38, n_per_group), + np.random.normal(2.8, 0.30, n_per_group), + np.random.normal(3.0, 0.30, n_per_group), + ] + ), + "species": ["setosa"] * n_per_group + ["versicolor"] * n_per_group + ["virginica"] * n_per_group, + } +) # Color palette (from style guide) colors = ["#306998", "#FFD43B", "#DC2626"] diff --git a/tests/unit/prompts/test_prompts.py b/tests/unit/prompts/test_prompts.py index 6fdf74a00c..660d6428e8 100644 --- a/tests/unit/prompts/test_prompts.py +++ b/tests/unit/prompts/test_prompts.py @@ -38,6 +38,7 @@ "plotnine.md", "pygal.md", "highcharts.md", + "letsplot.md", ] @@ -81,15 +82,21 @@ def quality_criteria_content(self) -> str: return (PROMPTS_DIR / "quality-criteria.md").read_text() def test_plot_generator_has_required_sections(self, plot_generator_content: str) -> None: - """Plot generator should have Role, Task, Rules sections.""" - required_sections = ["## Role", "## Task", "## Rules", "## Output"] + """Plot generator should have Role, Task, Output sections.""" + # Core sections at level 2 + required_sections = ["## Role", "## Task", "## Output"] for section in required_sections: assert section in plot_generator_content, f"Missing section: {section}" + # Rules can be at level 2 or 3 (### Rules under ## Output) + assert "Rules" in plot_generator_content, "Missing Rules section" def test_plot_generator_has_code_template(self, plot_generator_content: str) -> None: """Plot generator should include a code template.""" assert "```python" in plot_generator_content, "Missing Python code template" - assert "def create_plot" in plot_generator_content, "Missing create_plot function template" + # KISS style: simple scripts with comments, not functions + assert "# Create plot" in plot_generator_content or "plt.savefig" in plot_generator_content, ( + "Missing plot creation example" + ) def test_quality_criteria_has_scoring_section(self, quality_criteria_content: str) -> None: """Quality criteria should have scoring information.""" @@ -107,8 +114,9 @@ def test_library_prompt_has_required_sections(self, filename: str) -> None: content = (LIBRARY_PROMPTS_DIR / filename).read_text() library_name = filename.replace(".md", "") - # Check for header - assert f"# {library_name}" in content.lower(), f"Missing header for {library_name}" + # Check for header (normalize by removing hyphens for comparison) + content_normalized = content.lower().replace("-", "") + assert f"# {library_name}" in content_normalized, f"Missing header for {library_name}" # Check for import section assert "## Import" in content or "import" in content.lower(), f"Missing import section in {filename}" @@ -117,12 +125,13 @@ def test_library_prompt_has_required_sections(self, filename: str) -> None: assert "```python" in content, f"Missing Python code examples in {filename}" @pytest.mark.parametrize("filename", EXPECTED_LIBRARY_PROMPTS) - def test_library_prompt_has_return_type(self, filename: str) -> None: - """Each library prompt should specify return type.""" + def test_library_prompt_has_save_section(self, filename: str) -> None: + """Each library prompt should show how to save the plot.""" content = (LIBRARY_PROMPTS_DIR / filename).read_text() - # Either explicit return type section or type hint in code - has_return_type = "## Return Type" in content or "-> " in content - assert has_return_type, f"Missing return type specification in {filename}" + # KISS style: prompts show how to save, not function return types + save_patterns = ["## Save", "savefig", "save(", "write_image", "save_screenshot", "export_png"] + has_save_info = any(pattern in content for pattern in save_patterns) + assert has_save_info, f"Missing save/output section in {filename}" class TestNoPlaceholders: