From 72e89b1178e8b3f983ea544a4ad497099dc46249 Mon Sep 17 00:00:00 2001 From: Tim Hopper Date: Tue, 4 Aug 2026 20:30:56 -0400 Subject: [PATCH 1/2] Add lets-plot examples for all 13 plots Lets-Plot renders to PNG through ggsave; an IPython image/png formatter registered on PlotSpec keeps the example cells free of export boilerplate. It is imported as lp so its ggplot2-style names do not shadow plotnine's. --- Examples.ipynb | 243 +++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + render.py | 1 + tests/test_plots.py | 46 +++++++-- uv.lock | 57 +++++++++++ 5 files changed, 335 insertions(+), 13 deletions(-) diff --git a/Examples.ipynb b/Examples.ipynb index 548f724..1e7db06 100644 --- a/Examples.ipynb +++ b/Examples.ipynb @@ -5,7 +5,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": "%matplotlib inline\n\nimport readline\nimport altair as alt\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib import pyplot\nfrom plotnine import *\nimport numpy as np\n\nfrom plotly import figure_factory\nfrom plotly import graph_objects\nimport plotly.express as px\nfrom IPython.core.magic import Magics, magics_class, cell_magic\n\nfrom IPython.display import Image\n\nfrom pylab import rcParams\n\nsize = 20\nparams = {\n \"legend.fontsize\": size,\n \"figure.figsize\": (15, 5),\n \"axes.labelsize\": size,\n \"axes.titlesize\": size,\n \"xtick.labelsize\": size,\n \"ytick.labelsize\": size,\n \"axes.titlesize\": 1.5 * size,\n \"figure.figsize\": (12, 12),\n}\nrcParams.update(params)\ntheme_update(\n figure_size=(9, 9),\n title=element_text(size=size),\n text=element_text(size=0.6 * size),\n) # for plotnine\n\n\nimport plotly.io as pio\npio.renderers.default = \"png\"\npio.renderers[\"png\"].width = 750\npio.renderers[\"png\"].height = 750\n\n# Render Altair charts as PNG via vl-convert\nalt.renderers.enable(\"png\", scale_factor=2.0)" + "source": "%matplotlib inline\n\nimport readline\nimport altair as alt\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib import pyplot\nfrom plotnine import *\nimport numpy as np\n\nfrom plotly import figure_factory\nfrom plotly import graph_objects\nimport plotly.express as px\nfrom IPython.core.magic import Magics, magics_class, cell_magic\n\nfrom IPython.display import Image\n\nfrom pylab import rcParams\n\nsize = 20\nparams = {\n \"legend.fontsize\": size,\n \"figure.figsize\": (15, 5),\n \"axes.labelsize\": size,\n \"axes.titlesize\": size,\n \"xtick.labelsize\": size,\n \"ytick.labelsize\": size,\n \"axes.titlesize\": 1.5 * size,\n \"figure.figsize\": (12, 12),\n}\nrcParams.update(params)\ntheme_update(\n figure_size=(9, 9),\n title=element_text(size=size),\n text=element_text(size=0.6 * size),\n) # for plotnine\n\n\nimport plotly.io as pio\npio.renderers.default = \"png\"\npio.renderers[\"png\"].width = 750\npio.renderers[\"png\"].height = 750\n\n# Render Altair charts as PNG via vl-convert\nalt.renderers.enable(\"png\", scale_factor=2.0)\n\nimport tempfile\n\nimport lets_plot as lp\nfrom lets_plot.export import ggsave\n\nlp.LetsPlot.setup_html()\nlp.LetsPlot.set_theme(\n lp.theme(text=lp.element_text(size=16), title=lp.element_text(size=20))\n)\n\n_LETS_PLOT_DIR = tempfile.mkdtemp(prefix=\"lets-plot-\")\n\n\ndef _lets_plot_png(plot):\n \"\"\"Render a Lets-Plot spec to PNG so the cell output carries an image.\"\"\"\n path = ggsave(plot + lp.ggsize(750, 750), \"plot.png\", path=_LETS_PLOT_DIR)\n with open(path, \"rb\") as f:\n return f.read()\n\n\nget_ipython().display_formatter.formatters[\"image/png\"].for_type(\n lp.plot.core.PlotSpec, _lets_plot_png\n)" }, { "cell_type": "code", @@ -227,6 +227,28 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:bar-counts", + "package:lets-plot" + ] + }, + "outputs": [], + "source": [ + "\"\"\"Lets-Plot mirrors the ggplot2 grammar. It is imported as `lp` here\n", + "so its names don't collide with plotnine's.\n", + "\"\"\"\n", + "(lp.ggplot(mpg) +\n", + " lp.aes(x=\"manufacturer\") +\n", + " lp.geom_bar() +\n", + " lp.coord_flip() +\n", + " lp.ggtitle(\"Number of Cars by Make\"))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -344,6 +366,23 @@ " geom_histogram(binwidth=2))" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:simple-histogram", + "package:lets-plot" + ] + }, + "outputs": [], + "source": [ + "(lp.ggplot(mpg) +\n", + " lp.aes(x=\"cty\") +\n", + " lp.geom_histogram(binwidth=2))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -452,6 +491,26 @@ " ylab(\"Highway MPG\"))" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:scatter-plot", + "package:lets-plot" + ] + }, + "outputs": [], + "source": [ + "(lp.ggplot(mpg) +\n", + " lp.aes(x=\"displ\", y=\"hwy\") +\n", + " lp.geom_point() +\n", + " lp.ggtitle(\"Engine Displacement in Liters vs Highway MPG\") +\n", + " lp.xlab(\"Engine Displacement in Liters\") +\n", + " lp.ylab(\"Highway MPG\"))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -543,6 +602,24 @@ " geom_smooth(method=\"lm\"))" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:scatter-with-regression", + "package:lets-plot" + ] + }, + "outputs": [], + "source": [ + "(lp.ggplot(mpg) +\n", + " lp.aes(\"displ\", \"hwy\") +\n", + " lp.geom_point() +\n", + " lp.geom_smooth(method=\"lm\"))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -762,6 +839,26 @@ " ylab(\"Highway MPG\"))" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:scatter-plot-with-colors", + "package:lets-plot" + ] + }, + "outputs": [], + "source": [ + "(lp.ggplot(mpg) +\n", + " lp.aes(x=\"displ\", y=\"hwy\", color=\"class\") +\n", + " lp.geom_point() +\n", + " lp.ggtitle(\"Engine Displacement in Liters vs Highway MPG\") +\n", + " lp.xlab(\"Engine Displacement in Liters\") +\n", + " lp.ylab(\"Highway MPG\"))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -883,6 +980,23 @@ " geom_point(alpha=.5))" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:scatter-plot-with-size", + "package:lets-plot" + ] + }, + "outputs": [], + "source": [ + "(lp.ggplot(mpg) +\n", + " lp.aes(x=\"cty\", y=\"hwy\", size=\"cyl\") +\n", + " lp.geom_point(alpha=.5))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -989,6 +1103,24 @@ " facet_wrap(\" ~ c\", nrow = 2))" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:scatter-plot-with-facet", + "package:lets-plot" + ] + }, + "outputs": [], + "source": [ + "(lp.ggplot(mpg) +\n", + " lp.aes(x=\"displ\", y=\"hwy\") +\n", + " lp.geom_point() +\n", + " lp.facet_wrap(facets=\"class\", nrow=2))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1097,6 +1229,24 @@ " facet_grid(\"drv ~ cyl\"))" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:scatter-plot-with-facets", + "package:lets-plot" + ] + }, + "outputs": [], + "source": [ + "(lp.ggplot(mpg) +\n", + " lp.aes(x=\"displ\", y=\"hwy\") +\n", + " lp.geom_point() +\n", + " lp.facet_grid(x=\"cyl\", y=\"drv\"))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1214,6 +1364,26 @@ " ))" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:stacked-smooth-line-and-scatter", + "package:lets-plot" + ] + }, + "outputs": [], + "source": [ + "(lp.ggplot(mpg) +\n", + " lp.aes(x=\"displ\", y=\"hwy\") +\n", + " lp.geom_point(lp.aes(color=\"class\")) +\n", + " lp.geom_smooth(data=mpg[mpg[\"class\"] == \"subcompact\"],\n", + " se=False,\n", + " method=\"loess\"))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1301,6 +1471,23 @@ " geom_bar())" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:stacked-bar-chart", + "package:lets-plot" + ] + }, + "outputs": [], + "source": [ + "(lp.ggplot(diamonds) +\n", + " lp.aes(x=\"cut\", fill=\"clarity\") +\n", + " lp.geom_bar())" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1415,6 +1602,23 @@ " geom_bar(position = \"dodge\"))\n" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:dodged-bar-chart", + "package:lets-plot" + ] + }, + "outputs": [], + "source": [ + "(lp.ggplot(diamonds) +\n", + " lp.aes(x=\"cut\", fill=\"clarity\") +\n", + " lp.geom_bar(position=\"dodge\"))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1530,6 +1734,24 @@ " geom_density(alpha=0.1))" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:stacked-kde", + "package:lets-plot" + ] + }, + "outputs": [], + "source": [ + "(lp.ggplot(diamonds) +\n", + " lp.aes(\"depth\", fill=\"cut\", color=\"cut\") +\n", + " lp.geom_density(alpha=0.1) +\n", + " lp.xlim(55, 70))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1614,6 +1836,23 @@ " + geom_line())" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:timeseries", + "package:lets-plot" + ] + }, + "outputs": [], + "source": [ + "(lp.ggplot(ts)\n", + " + lp.aes(\"date\", \"value\")\n", + " + lp.geom_line())" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1686,4 +1925,4 @@ }, "nbformat": 4, "nbformat_minor": 1 -} \ No newline at end of file +} diff --git a/pyproject.toml b/pyproject.toml index e10669b..089edbf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ "matplotlib>=3.7", "seaborn>=0.13", "plotnine>=0.13", + "lets-plot>=4.11", "plotly>=5.24", "altair>=5.0", "vl-convert-python>=1.0", # Required for Altair PNG export diff --git a/render.py b/render.py index 7d06749..7173fc2 100644 --- a/render.py +++ b/render.py @@ -21,6 +21,7 @@ "matplotlib": "Matplotlib", "seaborn": "Seaborn", "plotnine": "plotnine", + "lets-plot": "lets-plot", "plotly": "plotly", "altair": "Altair", "ggplot": "ggplot2 (R)", diff --git a/tests/test_plots.py b/tests/test_plots.py index da53da9..4bd99c4 100644 --- a/tests/test_plots.py +++ b/tests/test_plots.py @@ -2,33 +2,57 @@ from collections import defaultdict defined_plots = { - "bar-counts": ["pandas", "plotnine", "ggplot", "plotly", "altair",], - "dodged-bar-chart": ["pandas", "plotnine", "ggplot", "plotly", "altair",], - "scatter-plot": ["pandas", "plotnine", "ggplot", "plotly", "altair",], + "bar-counts": [ + "pandas", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + ], + "dodged-bar-chart": [ + "pandas", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + ], + "scatter-plot": [ + "pandas", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + ], "scatter-plot-with-colors": [ "matplotlib", "seaborn", "plotnine", + "lets-plot", "ggplot", "plotly", "altair", ], - "scatter-plot-with-facet": ["seaborn", "plotnine", "ggplot", "plotly", "altair",], - "scatter-plot-with-facets": ["seaborn", "plotnine", "ggplot", "plotly", "altair",], - "scatter-plot-with-size": ["pandas", "plotnine", "ggplot", "plotly", "altair",], - "scatter-with-regression": ["seaborn", "plotnine", "ggplot", "plotly",], + "scatter-plot-with-facet": [ + "seaborn", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + ], + "scatter-plot-with-facets": [ + "seaborn", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + ], + "scatter-plot-with-size": [ + "pandas", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + ], + "scatter-with-regression": [ + "seaborn", "plotnine", "lets-plot", "ggplot", "plotly", + ], "simple-histogram": [ "pandas", "matplotlib", "plotnine", + "lets-plot", "ggplot", "plotly", "altair", ], - "stacked-bar-chart": ["pandas", "plotnine", "ggplot", "plotly", "altair",], - "stacked-kde": ["pandas", "seaborn", "plotnine", "ggplot", "plotly", "altair",], - "stacked-smooth-line-and-scatter": ["plotnine", "ggplot", "plotly", "altair",], - "timeseries": ["pandas", "plotnine", "ggplot", "plotly", "altair",], + "stacked-bar-chart": [ + "pandas", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + ], + "stacked-kde": [ + "pandas", "seaborn", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + ], + "stacked-smooth-line-and-scatter": [ + "plotnine", "lets-plot", "ggplot", "plotly", "altair", + ], + "timeseries": [ + "pandas", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + ], } diff --git a/uv.lock b/uv.lock index 574804b..1ff7ce9 100644 --- a/uv.lock +++ b/uv.lock @@ -1202,6 +1202,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, ] +[[package]] +name = "lets-plot" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "palettable" }, + { name = "pillow" }, + { name = "pypng" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/e8/df04b5b833c8fa89cbc7bcb5478166066c988fc302b050e8d58dc1bbf998/lets_plot-4.11.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:2114bdcbe73aafa1512779a3cd064e96b50fee37fca7ed78018aeaef224c8bc7", size = 7313995, upload-time = "2026-06-30T13:22:58.181Z" }, + { url = "https://files.pythonhosted.org/packages/82/3f/38578337f31bfc0491470023b0bc4514cf043880474d7d0eb8ccdc96ff73/lets_plot-4.11.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b5b72268fbce91b21cebd75028b79a9766584a79a4f2b278cedd5c3fcd9c13e8", size = 6861264, upload-time = "2026-06-30T13:22:48.03Z" }, + { url = "https://files.pythonhosted.org/packages/46/30/410281f6bd2ba7104308aeac08ae8472c0cc0812bd7ea934de2878453a3c/lets_plot-4.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:986fb279ca3e5aea05687374be97fb82a227814039d406ffc076ea71ff6fdcb1", size = 7175380, upload-time = "2026-06-30T13:22:46.201Z" }, + { url = "https://files.pythonhosted.org/packages/ae/56/e65ca7899579ec2a29cb4ee29f5ccadaefa7490cec2a056a77d97182ffa5/lets_plot-4.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7a36f999221513eb6d36fb7120c8b44bded1ff0138a3e5947a10560da62e9070", size = 7890925, upload-time = "2026-06-30T13:22:51.427Z" }, + { url = "https://files.pythonhosted.org/packages/96/d7/67adeaa76d6beabe30b359a83d99dcf793a71448055b9b1cbdad116d7504/lets_plot-4.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:212d140eda98138f1de4af2311c251b4cf6d71ccf956dd6d4976eb64eebd571d", size = 6599864, upload-time = "2026-06-30T13:22:49.658Z" }, + { url = "https://files.pythonhosted.org/packages/2c/78/d65586bcf7b58fe39aa06f945331a6d22a4663402284eddf6517a1cc48b6/lets_plot-4.11.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:33e0e85e20e3d15e4434cc4766a84369d0142e03fb21ddc15cca2aab3956d5aa", size = 7314019, upload-time = "2026-06-30T13:23:22.554Z" }, + { url = "https://files.pythonhosted.org/packages/77/f1/da86eef569031973cfccc707d60e171f37f2126af1b8d5246a9e033310a4/lets_plot-4.11.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2d04baafc53cc928710a9521237644f6e09f5c19586958977045eebdf0951621", size = 6861235, upload-time = "2026-06-30T13:22:56.624Z" }, + { url = "https://files.pythonhosted.org/packages/0e/ed/0acbc30b86ceeb886e04c30a8ba04a40b9e0808114f0dfc3cf077e7b6c01/lets_plot-4.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a162126d8199f1470f86a053d3c7f604baae2cbe7b7fea671f60d725e9f24117", size = 7175665, upload-time = "2026-06-30T13:22:53.151Z" }, + { url = "https://files.pythonhosted.org/packages/5a/97/4aec890acdd82eb1bfc0b39a7e93efe3740e1ae1cfd373e6960b2a2ed8c7/lets_plot-4.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:130c591553e0aa54d1340935c0c1ff9aec5a42b2a8f10f3b5bface8a88e08f82", size = 7890857, upload-time = "2026-06-30T13:22:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/44/58/52f4e3e765c12e683e47dc95935af665c97261c015eaacbcf89722aff07d/lets_plot-4.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:bdf2b6bad57763a834c745a8e845becd8e19e40c6cc0421868ed0067b7ae31fb", size = 6600021, upload-time = "2026-06-30T13:22:54.876Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2a/47e444e21ca3070fd8615c70ac2b93a91617fac9a6e5e13c9f73b6f8fe6b/lets_plot-4.11.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:ed10c01b1a7dd077a95522bd7ae2d84b05bd7331fd8f433ae9bcaa7b7bf1bb62", size = 7314018, upload-time = "2026-06-30T13:22:39.067Z" }, + { url = "https://files.pythonhosted.org/packages/5f/bc/7edb27519b1df5a31578f1049da0409df7294cdc10bf95c79a03444858bd/lets_plot-4.11.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:235bdbbca1245948b2cc97834ddd03d3c672701557bd2633a272975e63dc80c4", size = 6861236, upload-time = "2026-06-30T13:23:18.034Z" }, + { url = "https://files.pythonhosted.org/packages/28/5a/99a5223b29c76323fb3f0ac7aa9103771d06b0d0abb5bdf059327fdbfe0f/lets_plot-4.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3061176d3c940425878262b4f3d86bd4c22aa28f491c8a4b251b926ec0a18428", size = 7175635, upload-time = "2026-06-30T13:23:10.877Z" }, + { url = "https://files.pythonhosted.org/packages/a2/91/dd3b3def5958de3705a3b571c6a667f165fc44da41b3b6122fd7de0993bf/lets_plot-4.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:455101b29f0110b9aa22a5148c8ebd79c1be545e20592143e9792ca8e7a5ffd7", size = 7890804, upload-time = "2026-06-30T13:22:44.307Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f1/7546add6ac86af06047a966b5209ef2e28fd6bdd15f8a67fa4be1efd08ef/lets_plot-4.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:fbc89599b1bdd22611080bce3566aae9e15ed04702636bf1d8cc3cec140f9ac2", size = 6599865, upload-time = "2026-06-30T13:23:05.107Z" }, + { url = "https://files.pythonhosted.org/packages/51/6d/c6d3f068b5e86b7e92dc4f4ccc0c422c10ea6be898478032bf92a7ea97e5/lets_plot-4.11.0-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:b5e4c0949ee9729a59353fac2d74aa48c813de2a6cfbf5762521f911e6b05884", size = 7379628, upload-time = "2026-06-30T13:23:14.47Z" }, + { url = "https://files.pythonhosted.org/packages/4a/22/61fe4efb8e9cef175dd30813648e883345fed649a7b68f4be71fcb2f7cbc/lets_plot-4.11.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:d53da88c0e2448c811f44c6f3a71fba3b48ea471f6114467da2472ab09252146", size = 6861239, upload-time = "2026-06-30T13:23:26.232Z" }, + { url = "https://files.pythonhosted.org/packages/6a/43/e516ec41512f69153f9eb33da4b65067fb174bcc939a4a58bf5102c73473/lets_plot-4.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:681e4d11f539615122355f53ce910844de211c44654bb0a0aec9c204b36a1944", size = 7175688, upload-time = "2026-06-30T13:23:01.444Z" }, + { url = "https://files.pythonhosted.org/packages/81/0d/0066c34f0a1244cd8a13e7362486041c34289efb1ee822a64f7c3ce384f1/lets_plot-4.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:015f93dab1a4358d3a034bffff21ea16725ffb79d9fc113e4ff58d4ba0a13e91", size = 7890868, upload-time = "2026-06-30T13:23:06.794Z" }, + { url = "https://files.pythonhosted.org/packages/d5/30/e812334963aa9502e25af56e73abd2225af0e6fadc4db1e893374e9b1f10/lets_plot-4.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:2849191780092a3ee99cdcc1fd7733665c39900cc1872cf66b1a5f8779c53afe", size = 6599881, upload-time = "2026-06-30T13:23:03.241Z" }, + { url = "https://files.pythonhosted.org/packages/cf/84/9921fb975beae6d403d2ae8d9fa0e68d608a8d75c14e0c41c1a624d50bc4/lets_plot-4.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:060602fbe92eefa3151b0aaa6684e3d420652220dad771752cb4821a9cac1aa3", size = 6917001, upload-time = "2026-06-30T13:23:24.403Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9a/ea1deafd231061e2cce199085fbe37dde9ecc9810977dfb3cd33066ea01f/lets_plot-4.11.0-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:d5f8b1151266c41c0cd8b931c7a59a6d231b6892da58fbfcbee793f2052beee6", size = 7379483, upload-time = "2026-06-30T13:23:16.212Z" }, + { url = "https://files.pythonhosted.org/packages/97/9a/52d72f65db8d12bc94be0bf085dec94f3198e871d0c09ea9af9cfbc88f6f/lets_plot-4.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e5155eefc9829fe8775cc75a5ead0034a9673ba88813f11d8737594ba1253ee", size = 7177030, upload-time = "2026-06-30T13:23:20.6Z" }, + { url = "https://files.pythonhosted.org/packages/7a/2d/e8852858e64942e10438ca0235ea9a0d6064e17ef48e522d444681c62836/lets_plot-4.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f0925f7f9a7293115385af77452b9d03bfab64e8fc3bb1b84d5101ec0d339e8", size = 7890954, upload-time = "2026-06-30T13:22:42.308Z" }, + { url = "https://files.pythonhosted.org/packages/d9/21/f6fa7129af18153707ffe23aa7a89adc20225e7aa35976f0424e13c6940c/lets_plot-4.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:366410ddc5e063ca4c1685afd7c0b8f8dcd20946d3abc63d1024c3ef4f706992", size = 6599801, upload-time = "2026-06-30T13:22:40.752Z" }, +] + [[package]] name = "logistro" version = "2.0.1" @@ -1728,6 +1765,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, ] +[[package]] +name = "palettable" +version = "3.3.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/3d/a5854d60850485bff12f28abfe0e17f503e866763bed61aed4990b604530/palettable-3.3.3.tar.gz", hash = "sha256:094dd7d9a5fc1cca4854773e5c1fc6a315b33bd5b3a8f47064928facaf0490a8", size = 106639, upload-time = "2023-04-19T23:13:35.864Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/f7/3367feadd4ab56783b0971c9b7edfbdd68e0c70ce877949a5dd2117ed4a0/palettable-3.3.3-py2.py3-none-any.whl", hash = "sha256:74e9e7d7fe5a9be065e02397558ed1777b2df0b793a6f4ce1a5ee74f74fb0caa", size = 332251, upload-time = "2023-04-19T23:13:33.996Z" }, +] + [[package]] name = "pandas" version = "3.0.5" @@ -2086,6 +2132,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] +[[package]] +name = "pypng" +version = "0.20220715.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/93/cd/112f092ec27cca83e0516de0a3368dbd9128c187fb6b52aaaa7cde39c96d/pypng-0.20220715.0.tar.gz", hash = "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1", size = 128992, upload-time = "2022-07-15T14:11:05.301Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/b9/3766cc361d93edb2ce81e2e1f87dd98f314d7d513877a342d31b30741680/pypng-0.20220715.0-py3-none-any.whl", hash = "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c", size = 58057, upload-time = "2022-07-15T14:11:03.713Z" }, +] + [[package]] name = "pytest" version = "9.1.1" @@ -2135,6 +2190,7 @@ dependencies = [ { name = "jinja2-highlight" }, { name = "jupyter" }, { name = "kaleido" }, + { name = "lets-plot" }, { name = "markdown" }, { name = "matplotlib" }, { name = "notebook" }, @@ -2164,6 +2220,7 @@ requires-dist = [ { name = "jinja2-highlight", specifier = ">=0.6" }, { name = "jupyter", specifier = ">=1.0" }, { name = "kaleido", specifier = ">=1.0" }, + { name = "lets-plot", specifier = ">=4.11" }, { name = "markdown", specifier = ">=3.5" }, { name = "matplotlib", specifier = ">=3.7" }, { name = "notebook", specifier = ">=7.0" }, From 32d2273379ef02a608627689d022e6ad9ae6826a Mon Sep 17 00:00:00 2001 From: Tim Hopper Date: Tue, 4 Aug 2026 20:33:14 -0400 Subject: [PATCH 2/2] Describe Lets-Plot in the site intro and project docs --- CLAUDE.md | 4 ++-- INTRO.md | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index db66f9f..490136a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -pythonplot.com is a static website that provides a visual comparison of different Python plotting libraries (pandas, matplotlib, seaborn, plotnine, plotly, altair) and R's ggplot2 for exploratory data analysis. It serves as a "Rosetta Stone" showing how to create the same plots across different libraries. +pythonplot.com is a static website that provides a visual comparison of different Python plotting libraries (pandas, matplotlib, seaborn, plotnine, lets-plot, plotly, altair) and R's ggplot2 for exploratory data analysis. It serves as a "Rosetta Stone" showing how to create the same plots across different libraries. ## Architecture @@ -115,7 +115,7 @@ All plots are rendered to static PNG images inside the executed notebook: ## Dependencies -Python dependencies are declared in `pyproject.toml` and locked in `uv.lock` (committed). Key libraries: pandas, matplotlib, seaborn, plotnine, plotly (+kaleido), altair (+vl-convert-python), statsmodels, rpy2, Jinja2 with jinja2-highlight. +Python dependencies are declared in `pyproject.toml` and locked in `uv.lock` (committed). Key libraries: pandas, matplotlib, seaborn, plotnine, lets-plot, plotly (+kaleido), altair (+vl-convert-python), statsmodels, rpy2, Jinja2 with jinja2-highlight. R (system install) with ggplot2 and mgcv, installed by `setup_r.sh`. diff --git a/INTRO.md b/INTRO.md index ea93ade..9398ad4 100644 --- a/INTRO.md +++ b/INTRO.md @@ -29,6 +29,8 @@ There are several tools that can make the kinds of plots described here. At pres "[plotly](https://plot.ly/ "Plotly - Make charts and dashboards online")'s Python graphing library makes interactive, publication-quality graphs online. Examples of how to make line plots, scatter plots, area charts, bar charts, error bars, box plots, histograms, heatmaps, subplots, multiple-axes, polar charts, and bubble charts." I provide plotly examples rendered as static images. +"[Lets-Plot](https://lets-plot.org/ "Lets-Plot: an open-source plotting library for statistical data") is an open-source plotting library for statistical data," written by JetBrains and modeled on the grammar of graphics. Its Python API tracks ggplot2 closely enough that most of the examples below translate line for line. I provide Lets-Plot examples rendered as static images. + "[Bokeh](http://bokeh.pydata.org/en/latest/ "Python interactive visualization library") is a Python interactive visualization library that targets modern web browsers for presentation." "[bqplot](https://github.com/bloomberg/bqplot) is a Grammar of Graphics-based interactive plotting framework for the Jupyter notebook."