From 52da7f84f660c3d243f80bdf2bff3734ddbe49b0 Mon Sep 17 00:00:00 2001 From: Myles Scolnick Date: Wed, 4 Dec 2024 10:06:19 -0500 Subject: [PATCH] export markdown --- .github/workflows/export-notebooks.yml | 67 +++++++ .gitignore | 136 ++++++++++++++ README.md | 26 +++ generated/examples/ui/batch_and_form.py.md | 67 +++++++ generated/examples/ui/data_explorer.py.md | 88 +++++++++ generated/examples/ui/filterable_table.py.md | 65 +++++++ generated/examples/ui/inputs.py.md | 161 +++++++++++++++++ generated/examples/ui/layout.py.md | 68 +++++++ generated/examples/ui/mermaid.py.md | 47 +++++ generated/examples/ui/reactive_plots.py.md | 61 +++++++ generated/examples/ui/refresh.py.md | 95 ++++++++++ generated/examples/ui/table.py.md | 180 +++++++++++++++++++ generated/examples/ui/tabs.py.md | 50 ++++++ generated/examples/ui/task_list.py.md | 74 ++++++++ scripts/export_notebooks.py | 62 +++++++ 15 files changed, 1247 insertions(+) create mode 100644 .github/workflows/export-notebooks.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 generated/examples/ui/batch_and_form.py.md create mode 100644 generated/examples/ui/data_explorer.py.md create mode 100644 generated/examples/ui/filterable_table.py.md create mode 100644 generated/examples/ui/inputs.py.md create mode 100644 generated/examples/ui/layout.py.md create mode 100644 generated/examples/ui/mermaid.py.md create mode 100644 generated/examples/ui/reactive_plots.py.md create mode 100644 generated/examples/ui/refresh.py.md create mode 100644 generated/examples/ui/table.py.md create mode 100644 generated/examples/ui/tabs.py.md create mode 100644 generated/examples/ui/task_list.py.md create mode 100755 scripts/export_notebooks.py diff --git a/.github/workflows/export-notebooks.yml b/.github/workflows/export-notebooks.yml new file mode 100644 index 0000000..7f26803 --- /dev/null +++ b/.github/workflows/export-notebooks.yml @@ -0,0 +1,67 @@ +name: Export Notebooks + +on: + # Trigger on new releases from marimo-team/marimo + repository_dispatch: + types: [marimo-release] + + # Trigger on push to main + push: + branches: [main] + + # Trigger nightly + schedule: + - cron: '0 0 * * *' # Run at midnight UTC + + # Allow manual trigger + workflow_dispatch: {} + +permissions: + contents: write + +jobs: + export-notebooks: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: πŸš€ Install uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + + - name: 🐍 Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + + - name: πŸ“¦ Install marimo + run: | + uv pip install marimo + + - name: πŸ“‚ Clone marimo examples + run: | + git clone --depth 1 --filter=blob:none --sparse https://github.com/marimo-team/marimo.git + cd marimo + git sparse-checkout set examples + cd .. + mv marimo/examples . + rm -rf marimo + + - name: πŸ› οΈ Run export script + run: | + uv run scripts/export_notebooks.py + + - name: πŸ”„ Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + commit-message: 'Update exported notebooks' + branch: update-exported-notebooks + title: 'Update exported notebooks' + body: 'This PR updates the exported notebooks.' + labels: | + automated + assignees: | + mscolnick + path: 'generated/*' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b29e7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,136 @@ +# marimo +/examples + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case you do not want to do that, uncomment the following line to ignore it. +# Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyderworkspace + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e21e38 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Marimo Integration CI + +Automated CI pipeline for validating and exporting [marimo](https://github.com/marimo-team/marimo) example notebooks. + +## Features + +- Automatically exports whitelisted marimo notebooks to markdown +- Triggers on: + - New marimo releases + - Pushes to main + - Nightly builds + - Manual triggers + +## Setup + +1. Add notebooks to whitelist in `scripts/export_notebooks.py` +2. Configure repository dispatch webhook from marimo-team/marimo for release events +3. Ensure repository has proper write permissions for GitHub Actions + +## Usage + +The pipeline runs automatically on configured triggers. To run manually: + +1. Go to Actions tab +2. Select "Export Notebooks" workflow +3. Click "Run workflow" diff --git a/generated/examples/ui/batch_and_form.py.md b/generated/examples/ui/batch_and_form.py.md new file mode 100644 index 0000000..b674628 --- /dev/null +++ b/generated/examples/ui/batch_and_form.py.md @@ -0,0 +1,67 @@ +--- +title: Batch And Form +marimo-version: 0.9.29 +--- + +# Batch and Form + +Make custom UI elements using `batch()`, and turn any UI element +into a form with `form()`. + +```{.python.marimo} +reset + +variables = ( + mo.md( + """ + Choose your variable values + + {x} + + {y} + """ + ) + .batch( + x=mo.ui.slider(start=1, stop=10, step=1, label="$x =$"), + y=mo.ui.slider(start=1, stop=10, step=1, label="$y =$"), + ) + .form(show_clear_button=True, bordered=False) +) + +variables +``` + +```{.python.marimo} +if variables.value is not None: + submitted_values["x"].add(variables.value["x"]) + submitted_values["y"].add(variables.value["y"]) + +x = variables.value["x"] if variables.value else "\ldots" +y = variables.value["y"] if variables.value else "\ldots" + + +mo.md( + f""" + At the moment, + $x = {x}$ and $y = {y}$ + + All values ever assumed by $x$ and $y$ are + + {mo.hstack([mo.tree(submitted_values), reset], align="center", gap=4)} + """ +).callout() +``` + +```{.python.marimo} +reset + +submitted_values = {"x": set(), "y": set()} +``` + +```{.python.marimo} +reset = mo.ui.button(label="reset history") +``` + +```{.python.marimo} +import marimo as mo +``` \ No newline at end of file diff --git a/generated/examples/ui/data_explorer.py.md b/generated/examples/ui/data_explorer.py.md new file mode 100644 index 0000000..39de389 --- /dev/null +++ b/generated/examples/ui/data_explorer.py.md @@ -0,0 +1,88 @@ +--- +title: Data Explorer +marimo-version: 0.9.29 +width: full +--- + +# Data Explorer + +```{.python.marimo hide_code="true"} +sample = "https://github.com/vega/vega/blob/main/docs/data/stocks.csv" + +mo.md( + f""" + This notebook lets you upload a CSV and plot its columns. + + You can download a sample CSV if you'd like. + """ +) +``` + +```{.python.marimo hide_code="true"} +mo.md( + f""" + {mo.hstack([mo.md("**Upload a CSV.**")], justify="center")} + + {uploaded_file} + """ +) +``` + +```{.python.marimo} +mo.stop(not uploaded_file.name()) +df = pd.read_csv(io.StringIO(uploaded_file.contents().decode())) +``` + +```{.python.marimo} +mo.ui.table(df, page_size=5, selection=None) +``` + +```{.python.marimo} +plot_type = mo.ui.dropdown( + ["line", "hist"], value="line", label="Choose a plot type: " +) + +x_column = mo.ui.dropdown(df.columns, label="Choose x-axis: ") +y_column = mo.ui.dropdown(df.columns, label="Choose y-axis: ") +color_column = mo.ui.dropdown(df.columns, label="Choose color-axis: ") +``` + +```{.python.marimo} +mo.hstack( + [x_column, y_column, color_column, plot_type], justify="space-around" +).callout(kind="warn" if not x_column.value else "neutral") +``` + +```{.python.marimo} +mo.stop(not x_column.value) + + +def plot(x_column, y_column, color_column): + y_column = y_column or "count()" + title = f"{y_column} by {x_column}" + encoding = {"x": x_column, "y": y_column} + if color_column: + encoding["color"] = color_column + if plot_type.value == "line": + chart = alt.Chart(df).mark_line() + else: + chart = alt.Chart(df).mark_bar().encode(x=alt.X(x_column, bin=True)) + return chart.encode(**encoding).properties(title=title, width="container") + + +plot(x_column.value, y_column.value, color_column.value) +``` + +```{.python.marimo} +uploaded_file = mo.ui.file(filetypes=[".csv"], kind="area") +``` + +```{.python.marimo} +import marimo as mo +import altair as alt + + +import io +import matplotlib.pyplot as plt +import pandas as pd +``` \ No newline at end of file diff --git a/generated/examples/ui/filterable_table.py.md b/generated/examples/ui/filterable_table.py.md new file mode 100644 index 0000000..8b387b5 --- /dev/null +++ b/generated/examples/ui/filterable_table.py.md @@ -0,0 +1,65 @@ +--- +title: Filterable Table +marimo-version: 0.9.29 +width: medium +--- + +# Filterable DataFrame + +```{.python.marimo} +# Read the csv +df = pd.read_json(data_url("cars.json")) +``` + +```{.python.marimo} +# Create options for select widgets +manufacturer_options = df["Name"].str.split().str[0].unique() +manufacturer_options.sort() +cylinder_options = df["Cylinders"].unique().astype(str) +cylinder_options.sort() +``` + +```{.python.marimo} +# Create the filters +manufacturer = mo.ui.dropdown(manufacturer_options, label="Manufacturer") +cylinders = mo.ui.dropdown(cylinder_options, label="Cylinders") + +horse_power = mo.ui.range_slider.from_series( + df["Horsepower"], + show_value=True, +) + +mo.hstack([manufacturer, horse_power, cylinders], gap=3).left() +``` + +```{.python.marimo} +filter_df(df) +``` + +```{.python.marimo} +def filter_df(df): + filtered_df = df + if manufacturer.value: + filtered_df = filtered_df[ + filtered_df["Name"].str.contains(manufacturer.value, case=False) + ] + if cylinders.value: + filtered_df = filtered_df[filtered_df["Cylinders"] == cylinders.value] + if horse_power.value: + left, right = horse_power.value + filtered_df = filtered_df[ + (filtered_df["Horsepower"] >= left) + & (filtered_df["Horsepower"] <= right) + ] + return filtered_df +``` + +```{.python.marimo} +def data_url(file): + return f"https://cdn.jsdelivr.net/npm/vega-datasets@v1.29.0/data/{file}" +``` + +```{.python.marimo} +import marimo as mo +import pandas as pd +``` \ No newline at end of file diff --git a/generated/examples/ui/inputs.py.md b/generated/examples/ui/inputs.py.md new file mode 100644 index 0000000..9016755 --- /dev/null +++ b/generated/examples/ui/inputs.py.md @@ -0,0 +1,161 @@ +--- +title: Inputs +marimo-version: 0.9.29 +width: medium +--- + +# Inputs + +There are many way that a user can input with your notebook, such as text boxes, sliders, dates, and more. + +## Text boxes + +```{.python.marimo} +mo.hstack( + [ + username := mo.ui.text(label="Username"), + email := mo.ui.text(label="Email", kind="email"), + mo.ui.text(label="Password", kind="password"), + ] +) +``` + +```{.python.marimo} +mo.stop(not username.value, mo.md("What is your name?")) + +mo.md(f"πŸ‘‹ Hello {username.value}, nice to meet you!") +``` + +```{.python.marimo} +mo.ui.text_area( + label="A space for your thoughts", full_width=True, max_length=1000 +) +``` + +```{.python.marimo} +mo.ui.number(label="What is your favorite number?", start=0, stop=10) +``` + +## Sliders + +```{.python.marimo} +slider = mo.ui.slider(0, 100, value=50, label="Basic slider", show_value=True) +range_slider = mo.ui.range_slider( + 0, 100, value=(30, 70), label="Range slider", show_value=True +) +custom_steps = mo.ui.slider( + steps=[1, 10, 100, 1000], value=10, label="Custom steps", show_value=True +) +vertical = mo.ui.slider( + 0, 100, value=50, label="Vertical slider", orientation="vertical" +) +mo.vstack([slider, range_slider, custom_steps, vertical]).center() +``` + +## Checkboxes and Radios + +```{.python.marimo} +COLORS = ["red", "green", "blue"] +colors = mo.ui.array( + [mo.ui.checkbox(label=color) for color in COLORS], +) + +shape = mo.ui.radio( + ["circle", "square", "triangle"], inline=True, value="square" +) +mo.md(f""" +Let's build something: + +**Pick a shape:** + +{shape} + +**Pick a color:** + +{colors.hstack().left()} +""").center() +``` + +```{.python.marimo hide_code="true"} +selected_colors = [color for i, color in enumerate(COLORS) if colors.value[i]] + + +def draw_shape(shape, colors): + if not colors: + return "" + + gradient = "" + if isinstance(colors, list) and len(colors) > 1: + gradient_id = f"grad{hash(tuple(colors)) % 1000}" + stops = "".join( + [ + f'' + for i, color in enumerate(colors) + ] + ) + gradient = f'{stops}' + fill_color = f"url(#{gradient_id})" + else: + fill_color = colors if isinstance(colors, str) else colors[0] + + if shape == "circle": + html = f'{gradient}' + elif shape == "square": + html = f'{gradient}' + elif shape == "triangle": + html = f'{gradient}' + else: + html = "Shape not recognized" + return mo.Html(html) + + +mo.md(f""" +A {"/".join(selected_colors)} {shape.value}: +{draw_shape(shape.value, selected_colors)} +""").center() +``` + +## Dates + +```{.python.marimo} +import datetime + +start_date = mo.ui.date( + label="Start date", + start=datetime.date(2020, 1, 1), + stop=datetime.date(2020, 12, 31), +) +end_date = mo.ui.date( + label="End date", + start=datetime.date(2020, 1, 1), + stop=datetime.date(2020, 12, 31), +) +``` + +```{.python.marimo} +mo.hstack( + [ + mo.hstack([start_date, "➑️", end_date]).left(), + mo.md(f"From {start_date.value} to {end_date.value}"), + ] +) +``` + +## Dropdowns + +```{.python.marimo} +single = mo.ui.dropdown( + ["Option 1", "Option 2", "Option 3", "Option 4", "Option 5"], + label="Single select", +) +multi = mo.ui.multiselect( + ["Option 1", "Option 2", "Option 3", "Option 4", "Option 5"], + label="Multi select", + value=["Option 1", "Option 2"], +) +mo.hstack([single, multi]) +``` + +```{.python.marimo} +import marimo as mo +``` \ No newline at end of file diff --git a/generated/examples/ui/layout.py.md b/generated/examples/ui/layout.py.md new file mode 100644 index 0000000..24b6051 --- /dev/null +++ b/generated/examples/ui/layout.py.md @@ -0,0 +1,68 @@ +--- +title: Layout +marimo-version: 0.9.29 +--- + +# Stacks + +Use `mo.hstack` and `mo.vstack` to layout outputs in rows and columns. + +```{.python.marimo} +align = mo.ui.dropdown( + label="Align", options=["start", "end", "center", "stretch"] +) +justify = mo.ui.dropdown( + label="Justify", + options=["start", "center", "end", "space-between", "space-around"], +) +gap = mo.ui.number(label="Gap", start=0, stop=100, value=1) +size = mo.ui.slider(label="Size", start=60, stop=500) +wrap = mo.ui.checkbox(label="Wrap") + +mo.md( + f""" + **Stack parameters** + + {mo.hstack([align, justify, gap, wrap], gap=0.25)} + + **Boxes {size}** + """ +) +``` + +## Horizontal Stack: `hstack` + +```{.python.marimo} +mo.hstack( + boxes, + align=align.value, + justify=justify.value, + gap=gap.value, + wrap=wrap.value, +) +``` + +## Vertical Stack: `vstack` + +```{.python.marimo} +mo.vstack( + boxes, + align=align.value, + gap=gap.value, +) +``` + +```{.python.marimo} +def create_box(num): + box_size = size.value + num * 10 + return mo.Html( + f"
{str(num)}
" + ) + + +boxes = [create_box(i) for i in range(1, 5)] +``` + +```{.python.marimo} +import marimo as mo +``` \ No newline at end of file diff --git a/generated/examples/ui/mermaid.py.md b/generated/examples/ui/mermaid.py.md new file mode 100644 index 0000000..396940a --- /dev/null +++ b/generated/examples/ui/mermaid.py.md @@ -0,0 +1,47 @@ +--- +title: Mermaid +marimo-version: 0.9.29 +--- + +```{.python.marimo} +import marimo as mo +``` + +```{.python.marimo} +mo.mermaid( + """ +graph TD + A[Enter Chart Definition] --> B(Preview) + B --> C{decide} + C --> D[Keep] + C --> E[Edit Definition] + E --> B + D --> F[Save Image and Code] + F --> B +""" +).center() +``` + +```{.python.marimo} +graph = mo.ui.code_editor( + value="""sequenceDiagram + Alice->>John: Hello John, how are you? + John-->>Alice: Great! + Alice-)John: See you later!""", + language="mermaid", + label="Mermaid editor", +) +graph +``` + +```{.python.marimo} +mo.md( + f""" + You can render mermaid directly inside `mo.md`. Using + + `mo.mermaid()` + + {mo.mermaid(graph.value)} + """ +) +``` \ No newline at end of file diff --git a/generated/examples/ui/reactive_plots.py.md b/generated/examples/ui/reactive_plots.py.md new file mode 100644 index 0000000..e1c6056 --- /dev/null +++ b/generated/examples/ui/reactive_plots.py.md @@ -0,0 +1,61 @@ +--- +title: Reactive Plots +marimo-version: 0.9.29 +width: full +--- + +# Welcome to marimo! + +```{.python.marimo} +chart = mo.ui.altair_chart(scatter & bars) +chart +``` + +```{.python.marimo} +(filtered_data := mo.ui.table(chart.value)) +``` + +```{.python.marimo} +mo.stop(not len(filtered_data.value)) +mpg_hist = mo.ui.altair_chart( + alt.Chart(filtered_data.value) + .mark_bar() + .encode(alt.X("Miles_per_Gallon:Q", bin=True), y="count()") +) +horsepower_hist = mo.ui.altair_chart( + alt.Chart(filtered_data.value) + .mark_bar() + .encode(alt.X("Horsepower:Q", bin=True), y="count()") +) +mo.hstack([mpg_hist, horsepower_hist], justify="space-around", widths="equal") +``` + +```{.python.marimo} +cars = data.cars() +brush = alt.selection_interval() +scatter = ( + alt.Chart(cars) + .mark_point() + .encode( + x="Horsepower", + y="Miles_per_Gallon", + color="Origin", + ) + .add_params(brush) +) +bars = ( + alt.Chart(cars) + .mark_bar() + .encode(y="Origin:N", color="Origin:N", x="count(Origin):Q") + .transform_filter(brush) +) +``` + +```{.python.marimo} +import altair as alt +from vega_datasets import data +``` + +```{.python.marimo} +import marimo as mo +``` \ No newline at end of file diff --git a/generated/examples/ui/refresh.py.md b/generated/examples/ui/refresh.py.md new file mode 100644 index 0000000..138560a --- /dev/null +++ b/generated/examples/ui/refresh.py.md @@ -0,0 +1,95 @@ +--- +title: Refresh +marimo-version: 0.9.29 +width: full +--- + +```{.python.marimo} +mo.hstack([ + mo.vstack([ + mo.md("## Settings | [`marimo.ui.slider`](https://docs.marimo.io/api/inputs/slider.html), [`marimo.ui.refresh`](https://docs.marimo.io/recipes.html#run-a-cell-on-a-timer)\n---"), + refresh_interval_slider, + n_points_slider, + refresher, + mo.md("## ISS Positions | [`marimo.ui.altair_chart`](https://docs.marimo.io/api/plotting.html#marimo.ui.altair_chart)\n---"), + mo.as_html(chart).style({"width": "700px"}) + ], align="center"), + mo.vstack([ + mo.md("## Data | [`marimo.as_html`](https://docs.marimo.io/api/html.html)`(pd.DataFrame)`\n---"), + mo.as_html(iss_df) + ]) +], justify="center", wrap=True, gap=3) +``` + +```{.python.marimo} +hover=alt.selection_point(on="mouseover", clear="mouseout") + +# iss positions +iss_df = get_iss_positions() +iss = alt.Chart(iss_df[['longitude','latitude','timestamp']]).mark_circle( + stroke='black', size=100, +).encode( + longitude=alt.Longitude('longitude:Q'), + latitude='latitude:Q', + fill=alt.Fill('timestamp:Q', scale=alt.Scale(scheme='purples'), legend=None), + strokeWidth=alt.condition(hover, alt.value(3, empty=False), alt.value(0)), + tooltip=[ + alt.Tooltip('longitude:Q', title='Longitude', format='.4f'), + alt.Tooltip('latitude:Q', title='Latitude', format='.4f'), + alt.Tooltip('timestamp:T', title='Timestamp', format='%Y-%m-%d %H:%M:%S') + ] +).add_params(hover) + +chart = alt.layer(sphere, world, iss).project(type="naturalEarth1").properties(width=640, title="") +``` + +```{.python.marimo} +# load geo data from Vega Datasets +countries = alt.topo_feature(data.world_110m.url, 'countries') + +# world base +sphere = alt.Chart(alt.sphere()).mark_geoshape( + fill="aliceblue", stroke="black", strokeWidth=1.5 +) + +# world map +world = alt.Chart(countries).mark_geoshape( + fill="mintcream", stroke="black", strokeWidth=0.35 +) +``` + +```{.python.marimo} +def get_iss_positions(refresher=refresher): + refresher + timepoints = [int(time())] + while len(timepoints) <= n_points_slider.value: + timepoints.append(timepoints[-1] - refresh_interval_slider.value) + else: + timepoints.pop(0) + timepoints_str = str(timepoints)[1:-1].replace(" ", "") + iss_url = f"https://api.wheretheiss.at/v1/satellites/25544/positions?timestamps={timepoints_str}" + response = requests.get(iss_url) + df = pd.DataFrame(response.json()) + df['timestamp'] = pd.to_datetime(df.timestamp, unit='s') + return df[['timestamp','latitude','longitude','altitude','velocity','visibility']] +``` + +```{.python.marimo} +refresher = mo.ui.refresh(default_interval=f"{refresh_interval_slider.value}s") +``` + +```{.python.marimo} +refresh_interval_slider = mo.ui.slider(start=5, stop=60, step=1, value=10, label="refresh interval (default = 10 sec)") +n_points_slider = mo.ui.slider(start=5, stop=30, step=1, value=15, label="number of points (default = 15)") +``` + +```{.python.marimo} +import altair as alt +import marimo as mo +import pandas as pd +import requests +from time import time +from vega_datasets import data + +pd.options.display.max_rows = 30 +``` \ No newline at end of file diff --git a/generated/examples/ui/table.py.md b/generated/examples/ui/table.py.md new file mode 100644 index 0000000..9cc43f4 --- /dev/null +++ b/generated/examples/ui/table.py.md @@ -0,0 +1,180 @@ +--- +title: Table +marimo-version: 0.9.29 +--- + +# Tables + +> β€œSometimes I’ll start a sentence and I don’t even know where it’s going. I just hope I find it along the way.” +β€” Michael Scott + +_Create rich tables with selectable rows using_ `mo.ui.table`. + +**Single selection.** + +```{.python.marimo} +single_select_table = mo.ui.table( + office_characters, + selection="single", + pagination=True, +) +``` + +```{.python.marimo} +mo.ui.tabs({"table": single_select_table, "selection": single_select_table.value}) +``` + +**Multi-selection.** + +```{.python.marimo} +multi_select_table = mo.ui.table( + office_characters, + selection="multi", + pagination=True, +) +``` + +```{.python.marimo} +mo.ui.tabs({"table": multi_select_table, "selection": multi_select_table.value}) +``` + +**No selection.** + +```{.python.marimo} +table = mo.ui.table( + office_characters, + label="Employees", + selection=None, +) + +table +``` + +```{.python.marimo} +office_characters = [ + { + "first_name": "Michael", + "last_name": "Scott", + "skill": mo.ui.slider(1, 10, value=3), + "favorite place": mo.image(src="https://picsum.photos/100", rounded=True), + }, + { + "first_name": "Jim", + "last_name": "Halpert", + "skill": mo.ui.slider(1, 10, value=7), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Pam", + "last_name": "Beesly", + "skill": mo.ui.slider(1, 10, value=3), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Dwight", + "last_name": "Schrute", + "skill": mo.ui.slider(1, 10, value=7), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Angela", + "last_name": "Martin", + "skill": mo.ui.slider(1, 10, value=5), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Kevin", + "last_name": "Malone", + "skill": mo.ui.slider(1, 10, value=3), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Oscar", + "last_name": "Martinez", + "skill": mo.ui.slider(1, 10, value=3), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Stanley", + "last_name": "Hudson", + "skill": mo.ui.slider(1, 10, value=5), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Phyllis", + "last_name": "Vance", + "skill": mo.ui.slider(1, 10, value=5), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Meredith", + "last_name": "Palmer", + "skill": mo.ui.slider(1, 10, value=7), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Creed", + "last_name": "Bratton", + "skill": mo.ui.slider(1, 10, value=3), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Ryan", + "last_name": "Howard", + "skill": mo.ui.slider(1, 10, value=5), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Kelly", + "last_name": "Kapoor", + "skill": mo.ui.slider(1, 10, value=3), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Toby", + "last_name": "Flenderson", + "skill": mo.ui.slider(1, 10, value=3), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Darryl", + "last_name": "Philbin", + "skill": mo.ui.slider(1, 10, value=7), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Erin", + "last_name": "Hannon", + "skill": mo.ui.slider(1, 10, value=5), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Andy", + "last_name": "Bernard", + "skill": mo.ui.slider(1, 10, value=5), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Jan", + "last_name": "Levinson", + "skill": mo.ui.slider(1, 10, value=5), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "David", + "last_name": "Wallace", + "skill": mo.ui.slider(1, 10, value=3), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, + { + "first_name": "Holly", + "last_name": "Flax", + "skill": mo.ui.slider(1, 10, value=7), + "favorite place": mo.image(src="https://picsum.photos/100"), + }, +] +``` + +```{.python.marimo} +import marimo as mo +``` \ No newline at end of file diff --git a/generated/examples/ui/tabs.py.md b/generated/examples/ui/tabs.py.md new file mode 100644 index 0000000..89039e6 --- /dev/null +++ b/generated/examples/ui/tabs.py.md @@ -0,0 +1,50 @@ +--- +title: Tabs +marimo-version: 0.9.29 +--- + +# Tabs + +Use `mo.ui.tabs` to organize outputs. + +```{.python.marimo} +settings = mo.vstack( + [ + mo.md("Edit User"), + first := mo.ui.text(label="First Name"), + last := mo.ui.text(label="Last Name"), + ] +) + +organization = mo.vstack( + [ + mo.md("Edit Organization"), + org := mo.ui.text(label="Organization Name", value="..."), + employees := mo.ui.number( + label="Number of Employees", start=0, stop=1000 + ), + ] +) + +mo.ui.tabs( + { + "πŸ§™β€β™€ User": settings, + "🏒 Organization": organization, + } +) +``` + +```{.python.marimo} +mo.md( + f""" + Welcome **{first.value} {last.value}** to **{org.value}**! You are + employee no. **{employees.value + 1}**. + + #{"πŸŽ‰" * (min(employees.value + 1, 1000))} + """ +) if all([first.value, last.value, org.value]) else None +``` + +```{.python.marimo} +import marimo as mo +``` \ No newline at end of file diff --git a/generated/examples/ui/task_list.py.md b/generated/examples/ui/task_list.py.md new file mode 100644 index 0000000..43c73a6 --- /dev/null +++ b/generated/examples/ui/task_list.py.md @@ -0,0 +1,74 @@ +--- +title: Task List +marimo-version: 0.9.29 +--- + +```{.python.marimo hide_code="true"} +mo.md("# Task List").left() +``` + +```{.python.marimo} +@dataclass +class Task: + name: str + done: bool = False +``` + +```{.python.marimo} +get_tasks, set_tasks = mo.state([]) +mutation_signal, set_mutation_signal = mo.state(False) +``` + +```{.python.marimo} +mutation_signal + +task_entry_box = mo.ui.text(placeholder="a task ...") +``` + +```{.python.marimo} +def add_task(): + if task_entry_box.value: + set_tasks(lambda v: v + [Task(task_entry_box.value)]) + set_mutation_signal(True) + + +add_task_button = mo.ui.button( + label="add task", + on_change=lambda _: add_task(), +) + +clear_tasks_button = mo.ui.button( + label="clear completed tasks", + on_change=lambda _: set_tasks( + lambda v: [task for task in v if not task.done] + ), +) +``` + +```{.python.marimo} +mo.hstack( + [task_entry_box, add_task_button, clear_tasks_button], justify="start" +) +``` + +```{.python.marimo} +task_list = mo.ui.array( + [mo.ui.checkbox(value=task.done, label=task.name) for task in get_tasks()], + label="tasks", + on_change=lambda v: set_tasks( + [Task(task.name, done=v[i]) for i, task in enumerate(get_tasks())] + ), +) +``` + +```{.python.marimo} +mo.as_html(task_list) if task_list.value else mo.md("No tasks! πŸŽ‰") +``` + +```{.python.marimo} +import marimo as mo +``` + +```{.python.marimo} +from dataclasses import dataclass +``` \ No newline at end of file diff --git a/scripts/export_notebooks.py b/scripts/export_notebooks.py new file mode 100755 index 0000000..c2a9a2c --- /dev/null +++ b/scripts/export_notebooks.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +import argparse +import os +import subprocess + +# List of whitelisted notebooks to process +WHITELISTED_NOTEBOOKS: list[str] = [ + "ui/arrays_and_dicts.py", + "ui/batch_and_form.py", + "ui/data_explorer.py", + "ui/filterable_table.py", + "ui/inputs.py", + "ui/layout.py", + "ui/mermaid.py", + "ui/reactive_plots.py", + "ui/refresh.py", + "ui/table.py", + "ui/tabs.py", + "ui/task_list.py", +] + +def export_markdown(notebook_path: str) -> None: + """Export a single marimo notebook to markdown.""" + output_path = f"{notebook_path}.md" + print(f"Exporting {notebook_path} to {output_path}") + + result = subprocess.run( + ["marimo", "export", "md", notebook_path, "-o", "generated/" + output_path], + capture_output=True, + text=True, + ) + + if result.returncode != 0: + print(f"Error exporting {notebook_path}:") + print(result.stderr) + raise RuntimeError(f"Failed to export {notebook_path}") + +def main(): + parser = argparse.ArgumentParser(description="Export marimo notebooks to markdown") + parser.add_argument( + "--examples-dir", + type=str, + default="examples", + help="Directory containing example notebooks", + ) + args = parser.parse_args() + + # Ensure examples directory exists + if not os.path.exists(args.examples_dir): + raise FileNotFoundError(f"Examples directory not found: {args.examples_dir}") + + # Process each whitelisted notebook + for notebook in WHITELISTED_NOTEBOOKS: + notebook_path = os.path.join(args.examples_dir, notebook) + if os.path.exists(notebook_path): + export_markdown(notebook_path) + else: + print(f"Warning: Notebook not found: {notebook_path}") + +if __name__ == "__main__": + main()