diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9846eae51..7794acfb2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -63,8 +63,9 @@ repos: args: [--fix] additional_dependencies: # stylelint itself needs to be here when using additional_dependencies. - - stylelint@16.5.0 - - stylelint-config-standard-scss@13.1.0 + - stylelint + - stylelint-scss + - stylelint-config-standard-scss - repo: "https://github.com/pre-commit/pre-commit-hooks" rev: v5.0.0 diff --git a/.stylelintrc.json b/.stylelintrc.json index e43bfb5e5..d7076bd60 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,5 +1,6 @@ { "extends": "stylelint-config-standard-scss", + "plugins": ["stylelint-scss"], "rules": { "selector-class-pattern": null, "no-descending-specificity": null, diff --git a/docs/examples/code-cells.ipynb b/docs/examples/code-cells.ipynb new file mode 100644 index 000000000..6e2cd6b5a --- /dev/null +++ b/docs/examples/code-cells.ipynb @@ -0,0 +1,960 @@ +{ + "cells": [ + { + "cell_type": "raw", + "metadata": { + "nbsphinx": "hidden" + }, + "source": [ + "MIT License\n", + "\n", + "Copyright (c) 2015-2024 Matthias Geier\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a copy\n", + "of this software and associated documentation files (the \"Software\"), to deal\n", + "in the Software without restriction, including without limitation the rights\n", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", + "copies of the Software, and to permit persons to whom the Software is\n", + "furnished to do so, subject to the following conditions:\n", + "\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM\n", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", + "THE SOFTWARE.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This document is a modified copy of an example notebook in the nbsphinx docs.\n", + "The [original source](https://github.com/spatialaudio/nbsphinx/blob/9ee9da673b08e9db99ae1475fc83db6a62683e97/doc/code-cells.ipynb)\n", + "is in the nbsphinx GitHub repo." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Code Cells\n", + "\n", + "## Code, Output, Streams\n", + "\n", + "An empty code cell:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Two empty lines:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Leading/trailing empty lines:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 2 empty lines before, 1 after\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A simple output:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "6 * 7" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The standard output stream:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Hello, world!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Normal output + standard output" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Hello, world!\")\n", + "6 * 7" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The standard error stream is highlighted and displayed just below the code cell.\n", + "The standard output stream comes afterwards (with no special highlighting).\n", + "Finally, the \"normal\" output is displayed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "\n", + "\n", + "print(\"I'll appear on the standard error stream\", file=sys.stderr)\n", + "print(\"I'll appear on the standard output stream\")\n", + "\"I'm the 'normal' output\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "Note\n", + "\n", + "Using the IPython kernel, the order is actually mixed up,\n", + "see https://github.com/ipython/ipykernel/issues/280.\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Error / traceback" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "raises-exception" + ] + }, + "outputs": [], + "source": [ + "traceback # NOQA: F821" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Special Display Formats" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Local Image Files" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import Image\n", + "\n", + "\n", + "i = Image(filename=\"images/notebook_icon.png\")\n", + "i" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "display(i)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See also [SVG support for LaTeX](https://nbsphinx.readthedocs.io/en/0.9.7/markdown-cells.html#SVG-support-for-LaTeX)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import SVG\n", + "\n", + "\n", + "SVG(filename=\"images/python_logo.svg\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Image URLs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Image(url=\"https://www.python.org/static/img/python-logo-large.png\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Image(url=\"https://www.python.org/static/img/python-logo-large.png\", embed=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Image(url=\"https://jupyter.org/assets/homepage/main-logo.svg\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Math" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import Math\n", + "\n", + "\n", + "eq = Math(r\"\\int\\limits_{-\\infty}^\\infty f(x) \\delta(x - x_0) dx = f(x_0)\")\n", + "eq" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "display(eq)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import Latex\n", + "\n", + "\n", + "Latex(\n", + " r\"This is a \\LaTeX{} equation \"\n", + " r\"This is a \\LaTeX{} equation \"\n", + " r\"This is a \\LaTeX{} equation \"\n", + " r\"This is a \\LaTeX{} equation: $a^2 + b^2 = c^2$\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%latex\n", + "\\begin{equation}\n", + "\\int\\limits_{-\\infty}^\\infty f(x) \\delta(x - x_0) \\delta(x - x_0) \\delta(x - x_0) \\delta(x - x_0) \\delta(x - x_0) \\delta(x - x_0) dx = f(x_0)\n", + "\\end{equation}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plots" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "Make sure to use at least version 0.1.6 of the `matplotlib-inline` package\n", + "(which is an automatic dependency of the `ipython` package).\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By default, the plots created with the \"inline\" backend have the wrong size.\n", + "More specifically, PNG plots (the default) will be slightly larger than SVG and PDF plots.\n", + "\n", + "This can be fixed easily by creating a file named `matplotlibrc`\n", + "(in the directory where your Jupyter notebooks live,\n", + "e.g. in this directory: [matplotlibrc](https://github.com/spatialaudio/nbsphinx/blob/9ee9da673b08e9db99ae1475fc83db6a62683e97/doc/matplotlibrc)\n", + "and adding the following line:\n", + "\n", + " figure.dpi: 96\n", + "\n", + "If you are using Git to manage your files,\n", + "don't forget to commit this local configuration file to your repository.\n", + "Different directories can have different local configurations.\n", + "If a given configuration should apply to multiple directories,\n", + "symbolic links can be created in each directory.\n", + "\n", + "For more details, see\n", + "[Default Values for Matplotlib's \"inline\" Backend](https://nbviewer.org/github/mgeier/python-audio/blob/master/plotting/matplotlib-inline-defaults.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By default, plots are generated in the PNG format.\n", + "In most cases,\n", + "it looks better\n", + "if SVG plots are used for HTML output\n", + "and PDF plots are used for LaTeX/PDF.\n", + "This can be achieved by setting\n", + "[nbsphinx_execute_arguments](https://nbsphinx.readthedocs.io/en/0.9.7/configuration.html#nbsphinx_execute_arguments)\n", + "in your `conf.py` file like this:\n", + "\n", + "```python\n", + "nbsphinx_execute_arguments = [\n", + " \"--InlineBackend.figure_formats={'svg', 'pdf'}\",\n", + "]\n", + "```\n", + " \n", + "In the following example, `nbsphinx` should use an SVG image in the HTML output\n", + "and a PDF image for LaTeX/PDF output\n", + "(other Jupyter clients like JupyterLab will still show the default PNG format)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=[6, 3])\n", + "ax.plot([4, 9, 7, 20, 6, 33, 13, 23, 16, 62, 8]);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For comparison,\n", + "this is how it would look in PNG format ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%config InlineBackend.figure_formats = ['png']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and in `'png2x'` (a.k.a. `'retina'`) format:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%config InlineBackend.figure_formats = ['png2x']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Pandas Dataframes\n", + "\n", + "[Pandas dataframes](https://pandas.pydata.org/pandas-docs/stable/user_guide/dsintro.html#dataframe)\n", + "should be displayed as nicely formatted HTML tables (if you are using HTML output)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.DataFrame(\n", + " np.random.randint(0, 100, size=[10, 4]),\n", + " columns=[r\"$\\alpha$\", r\"$\\beta$\", r\"$\\gamma$\", r\"$\\delta$\"],\n", + ")\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Xarray Datasets\n", + "\n", + "[Xarray datasets](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.html)\n", + "should also be shown in their beautiful HTML representation.\n", + "LaTeX/PDF output will fall back to the plain text representation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import xarray as xr" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "a = {}\n", + "for name in [\"t\", \"q\"]:\n", + " da = xr.DataArray(\n", + " [\n", + " [[11, 12, 13], [21, 22, 23], [31, 32, 33]],\n", + " [[14, 15, 16], [24, 25, 26], [34, 35, 36]],\n", + " ],\n", + " coords={\"level\": [\"500\", \"700\"], \"x\": [1, 2, 3], \"y\": [4, 5, 6]},\n", + " name=name,\n", + " attrs={\"param\": name},\n", + " )\n", + " a[name] = da\n", + "\n", + "ds = xr.Dataset(a, attrs={\"title\": \"Demo dataset\", \"year\": 2024})\n", + "ds" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Markdown Content" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import Markdown" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "md = Markdown(\"\"\"\n", + "# Markdown\n", + "\n", + "It *should* show up as **formatted** text\n", + "with things like [links] and images.\n", + "\n", + "[links]: https://jupyter.org/\n", + "\n", + "![Jupyter notebook icon](images/notebook_icon.png)\n", + "\n", + "## Markdown Extensions\n", + "\n", + "There might also be mathematical equations like\n", + "$a^2 + b^2 = c^2$\n", + "and even tables:\n", + "\n", + "A | B | A and B\n", + "------|-------|--------\n", + "False | False | False\n", + "True | False | False\n", + "False | True | False\n", + "True | True | True\n", + "\n", + "\"\"\")\n", + "md" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### YouTube Videos" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import YouTubeVideo\n", + "\n", + "\n", + "YouTubeVideo(\"9_OIs49m56E\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Interactive Widgets (HTML only)\n", + "\n", + "The basic widget infrastructure is provided by\n", + "the [ipywidgets](https://ipywidgets.readthedocs.io/) module.\n", + "More advanced widgets are available in separate packages,\n", + "see for example https://jupyter.org/widgets.\n", + "\n", + "The JavaScript code which is needed to display Jupyter widgets\n", + "is loaded automatically (using RequireJS).\n", + "If you want to use non-default URLs or local files,\n", + "you can use the\n", + "[nbsphinx_widgets_path](https://nbsphinx.readthedocs.io/en/0.9.7/configuration.html#nbsphinx_widgets_path) and\n", + "[nbsphinx_requirejs_path](https://nbsphinx.readthedocs.io/en/0.9.7/configuration.html#nbsphinx_requirejs_path)\n", + "settings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import ipywidgets as w" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "slider = w.IntSlider()\n", + "slider.value = 42\n", + "slider" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A widget typically consists of a so-called \"model\" and a \"view\" into that model.\n", + "\n", + "If you display a widget multiple times,\n", + "all instances act as a \"view\" into the same \"model\".\n", + "That means that their state is synchronized.\n", + "You can move either one of these sliders to try this out:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "slider" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also link different widgets.\n", + "\n", + "Widgets can be linked via the kernel\n", + "(which of course only works while a kernel is running)\n", + "or directly in the client\n", + "(which even works in the rendered HTML pages).\n", + "\n", + "Widgets can be linked uni- or bi-directionally.\n", + "\n", + "Examples for all 4 combinations are shown here:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "link = w.IntSlider(description=\"link\")\n", + "w.link((slider, \"value\"), (link, \"value\"))\n", + "jslink = w.IntSlider(description=\"jslink\")\n", + "w.jslink((slider, \"value\"), (jslink, \"value\"))\n", + "dlink = w.IntSlider(description=\"dlink\")\n", + "w.dlink((slider, \"value\"), (dlink, \"value\"))\n", + "jsdlink = w.IntSlider(description=\"jsdlink\")\n", + "w.jsdlink((slider, \"value\"), (jsdlink, \"value\"))\n", + "w.VBox([link, jslink, dlink, jsdlink])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tabs = w.Tab()\n", + "for idx, obj in enumerate([df, fig, eq, i, md, slider]):\n", + " out = w.Output()\n", + " with out:\n", + " display(obj)\n", + " tabs.children += (out,)\n", + " tabs.set_title(idx, obj.__class__.__name__)\n", + "tabs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "Other Languages\n", + "\n", + "The examples shown here are using Python,\n", + "but the widget technology can also be used with\n", + "different Jupyter kernels\n", + "(i.e. with different programming languages).\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Troubleshooting\n", + "\n", + "To obtain more information if widgets are not displayed as expected, you will need to look at the error message in the web browser console.\n", + "\n", + "> To figure out how to open the web browser console, you may look at the web browser documentation: \n", + "> Chrome: https://developer.chrome.com/docs/devtools/open/#shortcuts \n", + "> Firefox: https://developer.mozilla.org/en-US/docs/Tools/Web_Console#opening-the-web-console\n", + "\n", + "The error is most probably linked to the JavaScript files not being loaded or loaded in the wrong order within the HTML file. To analyze the error, you can inspect the HTML file within the web browser (e.g.: right-click on the page and select *View Page Source*) and look at the `` section of the page. That section should contain\n", + "some JavaScript libraries. Those relevant for widgets are:\n", + "\n", + "```html\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "```\n", + "\n", + "The two first elements are mandatory. The third one is required only if you designed your own widgets but did not publish them on npm.js.\n", + "\n", + "If those libraries appear in a different order, the widgets won't be displayed. \n", + "\n", + "Here is a list of possible solutions:\n", + "\n", + "- If the widgets are **not displayed**, see [#519](https://github.com/spatialaudio/nbsphinx/issues/519).\n", + "- If the widgets are **displayed multiple times**, see [#378](https://github.com/spatialaudio/nbsphinx/issues/378)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Arbitrary JavaScript Output (HTML only)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%javascript\n", + "\n", + "var text = document.createTextNode(\"Hello, I was generated with JavaScript!\");\n", + "// Content appended to \"element\" will be visible in the output area:\n", + "element.appendChild(text);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Unsupported Output Types\n", + "\n", + "If a code cell produces data with an unsupported MIME type, the Jupyter Notebook doesn't generate any output.\n", + "`nbsphinx`, however, shows a warning message." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "display(\n", + " {\n", + " \"text/x-python\": 'print(\"Hello, world!\")',\n", + " \"text/x-haskell\": 'main = putStrLn \"Hello, world!\"',\n", + " },\n", + " raw=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ANSI Colors\n", + "\n", + "The standard output and standard error streams may contain [ANSI escape sequences](https://en.wikipedia.org/wiki/ANSI_escape_code) to change the text and background colors." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"BEWARE: \\x1b[1;33;41mugly colors\\x1b[m!\", file=sys.stderr)\n", + "print(\n", + " \"AB\\x1b[43mCD\\x1b[35mEF\\x1b[1mGH\\x1b[4mIJ\\x1b[7m\"\n", + " \"KL\\x1b[49mMN\\x1b[39mOP\\x1b[22mQR\\x1b[24mST\\x1b[27mUV\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following code showing the 8 basic ANSI colors is based on https://web.archive.org/web/20231225185739/https://tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html.\n", + "Each of the 8 colors has an \"intense\" variation, which is used for bold text." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "text = \" XYZ \"\n", + "formatstring = \"\\x1b[{}m\" + text + \"\\x1b[m\"\n", + "\n", + "print(\n", + " \" \" * 6\n", + " + \" \" * len(text)\n", + " + \"\".join(\"{:^{}}\".format(bg, len(text)) for bg in range(40, 48))\n", + ")\n", + "for fg in range(30, 38):\n", + " for bold in False, True:\n", + " fg_code = (\"1;\" if bold else \"\") + str(fg)\n", + " print(\n", + " \" {:>4} \".format(fg_code)\n", + " + formatstring.format(fg_code)\n", + " + \"\".join(\n", + " formatstring.format(fg_code + \";\" + str(bg)) for bg in range(40, 48)\n", + " )\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "ANSI also supports a set of 256 indexed colors.\n", + "The following code showing all of them is based on [http://bitmote.com/index.php?post/2012/11/19/Using-ANSI-Color-Codes-to-Colorize-Your-Bash-Prompt-on-Linux](https://web.archive.org/web/20190109005413/http://bitmote.com/index.php?post/2012/11/19/Using-ANSI-Color-Codes-to-Colorize-Your-Bash-Prompt-on-Linux)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "formatstring = \"\\x1b[38;5;{0};48;5;{0}mX\\x1b[1mX\\x1b[m\"\n", + "\n", + "print(\" + \" + \"\".join(\"{:2}\".format(i) for i in range(36)))\n", + "print(\" 0 \" + \"\".join(formatstring.format(i) for i in range(16)))\n", + "for i in range(7):\n", + " i = i * 36 + 16\n", + " print(\n", + " \"{:3} \".format(i)\n", + " + \"\".join(formatstring.format(i + j) for j in range(36) if i + j < 256)\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can even use 24-bit RGB colors:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "start = 255, 0, 0\n", + "end = 0, 0, 255\n", + "length = 79\n", + "out = []\n", + "\n", + "for i in range(length):\n", + " rgb = [start[c] + int(i * (end[c] - start[c]) / length) for c in range(3)]\n", + " out.append(\n", + " \"\\x1b[\"\n", + " \"38;2;{rgb[2]};{rgb[1]};{rgb[0]};\"\n", + " \"48;2;{rgb[0]};{rgb[1]};{rgb[2]}mX\\x1b[m\".format(rgb=rgb)\n", + " )\n", + "print(\"\".join(out))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/images/notebook_icon.png b/docs/examples/images/notebook_icon.png new file mode 100644 index 000000000..5e50eee51 Binary files /dev/null and b/docs/examples/images/notebook_icon.png differ diff --git a/docs/examples/images/python_logo.svg b/docs/examples/images/python_logo.svg new file mode 100644 index 000000000..116eaac31 --- /dev/null +++ b/docs/examples/images/python_logo.svg @@ -0,0 +1,269 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/examples/index.rst b/docs/examples/index.rst index ad62877e8..0bc5023b6 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -17,6 +17,7 @@ See the sections in the primary sidebar and below to explore. kitchen-sink/index pydata + code-cells execution graphviz diff --git a/docs/examples/pydata.ipynb b/docs/examples/pydata.ipynb index b8de05084..c2a19c675 100644 --- a/docs/examples/pydata.ipynb +++ b/docs/examples/pydata.ipynb @@ -36,6 +36,55 @@ "df" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All the cells in the previous data frame are roughly the same length/width. The next table has more varied types of data. This is useful for checking the effects of CSS." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# car data taken from Bokeh sample data (`pip install bokeh-sampledata`)\n", + "\n", + "# from bokeh_sampledata.autompg2 import autompg2 as mpg\n", + "# del mpg['Unnamed 0']\n", + "# mpg_csv = mpg.sample(n=20).to_csv()\n", + "\n", + "import io\n", + "\n", + "\n", + "mpg_csv = io.StringIO(\n", + " \",manufacturer,model,displ,year,cyl,trans,drv,cty,hwy,fl,class\\n\"\n", + " \"162,Subaru,Forester Awd,2.5,2008,4,manual(m5),4x4,19,25,p,suv\\n\"\n", + " \"36,Chevrolet,Malibu,3.6,2008,6,auto(s6),front,17,26,r,midsize\\n\"\n", + " \"133,Land Rover,Range Rover,4.6,1999,8,auto(l4),4x4,11,15,p,suv\\n\"\n", + " \"102,Honda,Civic,1.6,1999,4,manual(m5),front,23,29,p,subcompact\\n\"\n", + " \"47,Dodge,Caravan 2wd,4.0,2008,6,auto(l6),front,16,23,r,minivan\\n\"\n", + " \"84,Ford,F150 Pickup 4wd,4.2,1999,6,manual(m5),4x4,14,17,r,pickup\\n\"\n", + " \"168,Subaru,Impreza Awd,2.5,1999,4,auto(l4),4x4,19,26,r,subcompact\\n\"\n", + " \"149,Nissan,Maxima,3.5,2008,6,auto(av),front,19,25,p,midsize\\n\"\n", + " \"158,Pontiac,Grand Prix,5.3,2008,8,auto(s4),front,16,25,p,midsize\\n\"\n", + " \"50,Dodge,Dakota Pickup 4wd,3.9,1999,6,auto(l4),4x4,13,17,r,pickup\\n\"\n", + " \"58,Dodge,Durango 4wd,4.7,2008,8,auto(l5),4x4,13,17,r,suv\\n\"\n", + " \"28,Chevrolet,K1500 Tahoe 4wd,5.3,2008,8,auto(l4),4x4,14,19,r,suv\\n\"\n", + " \"116,Hyundai,Tiburon,2.0,1999,4,manual(m5),front,19,29,r,subcompact\\n\"\n", + " \"97,Ford,Mustang,4.6,2008,8,auto(l5),rear,15,22,r,subcompact\\n\"\n", + " \"80,Ford,Explorer 4wd,4.0,2008,6,auto(l5),4x4,13,19,r,suv\\n\"\n", + " \"232,Volkswagen,Passat,2.8,1999,6,manual(m5),front,18,26,p,midsize\\n\"\n", + " \"219,Volkswagen,Jetta,2.8,1999,6,auto(l4),front,16,23,r,compact\\n\"\n", + " \"72,Dodge,Ram 1500 Pickup 4wd,5.7,2008,8,auto(l5),4x4,13,17,r,pickup\\n\"\n", + " \"200,Toyota,Toyota Tacoma 4wd,2.7,1999,4,manual(m5),4x4,15,20,r,pickup\\n\"\n", + " \"145,Nissan,Altima,3.5,2008,6,manual(m6),front,19,27,p,midsize\\n\"\n", + ")\n", + "mpg_df = pd.read_csv(mpg_csv, index_col=0)\n", + "mpg_df" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/src/pydata_sphinx_theme/assets/styles/base/_base.scss b/src/pydata_sphinx_theme/assets/styles/base/_base.scss index 82235df44..e775c073c 100644 --- a/src/pydata_sphinx_theme/assets/styles/base/_base.scss +++ b/src/pydata_sphinx_theme/assets/styles/base/_base.scss @@ -153,7 +153,7 @@ code { color: var(--pst-color-inline-code); } -pre { +%pre-style { margin: 1.5em 0; padding: 1rem; background-color: var(--pst-color-surface); @@ -169,6 +169,10 @@ pre { } } +pre { + @extend %pre-style; +} + // the back to top btn #pst-back-to-top { // zindex-tooltip comes from Bootstrap https://getbootstrap.com/docs/5.2/layout/z-index/ diff --git a/src/pydata_sphinx_theme/assets/styles/extensions/_notebooks.scss b/src/pydata_sphinx_theme/assets/styles/extensions/_notebooks.scss index 2f1a9c103..a8f2c0f75 100644 --- a/src/pydata_sphinx_theme/assets/styles/extensions/_notebooks.scss +++ b/src/pydata_sphinx_theme/assets/styles/extensions/_notebooks.scss @@ -9,84 +9,202 @@ /******************************************************************************* * nbsphinx */ -html div.rendered_html, -// NBsphinx ipywidgets output selector -html .jp-RenderedHTMLCommon { - // Add some margin around the element box for the focus ring. Otherwise the - // focus ring gets clipped because the containing elements have `overflow: - // hidden` applied to them (via the `.lm-Widget` selector) - margin: $focus-ring-width; - - table { - table-layout: auto; - } -} -.bd-content .nboutput { - .output_area { - &.rendered_html, - .jp-RenderedHTMLCommon { - // pandas - table.dataframe { - @include table-colors; - - tbody { - tr:hover { - background-color: var(--pst-color-table-row-hover-bg); - } +// The following block implements Isabela's design for notebook code cells: +// https://github.com/Quansight-Labs/czi-scientific-python-mgmt/issues/142#issuecomment-2759132381. +@mixin -nbsphinx-restyle { + $space-between-vertical-bar-and-cell: 1em; + $top-and-bottom-container-space: 1.2em; + + // Common styles for both input and output containers + div.nbinput.container, + div.nboutput.container { + display: block; // override nbsphinx display: flex + position: relative; // needed for the absolute positioning of the ::before pseudo-element + padding-left: $space-between-vertical-bar-and-cell; + + // This is the vertical bar that connects the input and output containers + &::before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 0.2em; + background: var(--pst-color-success); + + // TODO: figure out a way to create a single vertical bar that runs the + // entire height of both the input and output cells, rather than two + // stacked bars, that way we can add border radius to the ends without it + // looking funky. + + // border-radius: 1px; + } + + // This is the label for the input or output + div.prompt { + width: auto; // override nbsphinx + margin-bottom: 0.7em; // space between the label and the input or output + padding: 0; // override nbsphinx, we control the whitespace around the label with other rules + + div.highlight { + position: static; // override nbsphinx - position: absolute + + pre { + color: inherit; // override nbsphinx - get the color from PST rules applied to the parent elements } } } + } + + // Input-specific styles + div.nbinput.container { + margin-top: $top-and-bottom-container-space; // space between the top of the notebook cell and the surrounding content + padding-top: 0; // override nbsphinx - let the apparent whitespace be controlled by the margin-top rule above + + // Prefix the cell number with the word "In". + // TODO: add to translations + div.prompt pre::before { + content: "In "; + } + + div.input_area { + border: none; // border taken care of by
 styles elsewhere in the theme
+      overflow: visible;
+
+      > div.highlight {
+        overflow: visible; // necessary for focus ring
+      }
+    }
+  }
 
-    // Dark theme special-cases
-    html[data-theme="dark"] & {
-      &.rendered_html:not(:has(table.dataframe)),
-      // ipywidgets
-      .widget-subarea {
-        @include cell-output-background;
+  // Output-specific styles
+  div.nboutput.container {
+    // Space between the input portion and the output portion. Cannot use margin
+    // here because we do not want to create a break in the vertical bar running
+    // down the left side.
+    padding-top: $top-and-bottom-container-space;
+
+    // Prefix the cell number with the word "Out". TODO: add to translations
+    div.prompt pre::before {
+      content: "Out ";
+    }
+
+    div.output_area {
+      &.rendered_html,
+      div[class*="highlight"].math,
+      > div.output_javascript,
+      > img {
+        // Override nbsphinx. We want the left edge of the output area to be
+        // left-aligned with the output label, the input area, and the input
+        // label.
+        padding: 0;
       }
 
+      // Override nbsphinx. Things like box shadows on elements that are
+      // children of div.output_area do not work if the overflow is not set to
+      // visible. But do not apply to .rendered_html because it could be, for
+      // example, a long pandas data table that is rendered without a
+      // non-overflowing container.
+      &:not(.rendered_html) {
+        overflow: visible;
+      }
+
+      // What this selector attempts to do is to override a bunch of CSS rules
+      // that come from multiple places that add top and bottom whitespace to elements,
+      // like paragraph elements. These rules mess up the alignment between
+      // bottom of the notebook cell content and the left bar that we've added.
+      // They also create unnecessary extra whitespace at the bottom of notebook
+      // cells. However, this selector may be a bit too wide, too blunt. We may
+      // need find a different way to achieve this.
+      :not(.highlight, .widget-tab-contents, .jupyter-widgets):last-child {
+        margin-top: 0;
+        margin-bottom: 0;
+      }
+
+      div.highlight,
+      div.nohighlight,
+      .jupyter-widgets,
+      .jupyter-matplotlib-canvas-container {
+        overflow: visible; // for focus ring
+      }
+
+      // use theme color for stderr
       &.stderr {
-        background-color: var(--pst-color-danger);
+        background-color: transparent;
+
+        div[class*="highlight"] > pre {
+          background-color: var(--pst-color-danger-bg);
+        }
+      }
+
+      // Override our own style rule in _math.scss
+      div.math {
+        display: block;
+      }
+
+      span.math {
+        display: inline;
       }
     }
   }
-}
 
-// Add extra padding to the final item in an nbsphinx container
-div.nblast.container {
-  margin-bottom: 1rem;
-}
-
-// Override nbsphinx's colors for notebook cell prompts because they do not have
-// sufficient contrast. Colors chosen from accessible-pygments
-// a11y-high-contrast-{light,dark} themes.
+  // Override nbsphinx's styles with our own 
 tag styles
+  div.nbinput.container div.input_area div[class*="highlight"] > pre,
+  div.nboutput.container div.output_area div[class*="highlight"] > pre {
+    @extend %pre-style;
 
-// Notebook cell input line number. Replace nbsphinx's low contrast blue with
-// higher contrast blues.
-.nbinput.container .prompt pre {
-  html[data-theme="light"] & {
-    // Copied from accessible-pygments [a11y-high-contrast-light](https://github.com/Quansight-Labs/accessible-pygments/tree/main/a11y_pygments/a11y_high_contrast_light)
-    color: #005b82;
+    margin: 0;
   }
 
-  html[data-theme="dark"] & {
-    // Copied from accessible-pygments [a11y-high-contrast-dark](https://github.com/Quansight-Labs/accessible-pygments/tree/main/a11y_pygments/a11y_high_contrast_dark)
-    color: #00e0e0;
+  // Last container styles
+  div.container.nblast {
+    margin-bottom: $top-and-bottom-container-space;
+
+    // Override nbsphinx's padding rule. Let the whitespace be controlled by the
+    // margin-bottom rule above. This is important because if we create
+    // whitespace with padding instead of margin, then we create a misalignment
+    // between the vertical left bar and the bottom of the notebook cell.
+    padding-bottom: 0;
   }
 }
 
-// Notebook cell output line number. Replace nbsphinx's low contrast red with
-// higher contrast red / orange.
-.nboutput.container .prompt pre {
-  html[data-theme="light"] & {
-    // Copied from accessible-pygments [a11y-high-contrast-light](https://github.com/Quansight-Labs/accessible-pygments/tree/main/a11y_pygments/a11y_high_contrast_light)
-    color: #a12236;
-  }
+// Nest under .bd-article-container so that selectors have higher specificity
+// and can therefore override the rules in the nbphinx stylesheet.
+.bd-article-container {
+  @include _nbsphinx-restyle;
 
-  html[data-theme="dark"] & {
-    // Copied from accessible-pygments [a11y-high-contrast-dark](https://github.com/Quansight-Labs/accessible-pygments/tree/main/a11y_pygments/a11y_high_contrast_dark)
-    color: #ffa07a;
+  .nboutput {
+    .output_area {
+      &div.rendered_html,
+      .jp-RenderedHTMLCommon {
+        table {
+          // Override nbsphinx's `table-layout: fixed` rule. GitHub issue #952:
+          // https://github.com/pydata/pydata-sphinx-theme/issues/952
+          table-layout: auto;
+
+          // pandas
+          &.dataframe {
+            @include table-colors;
+
+            tbody {
+              tr:hover {
+                background-color: var(--pst-color-table-row-hover-bg);
+              }
+            }
+          }
+        }
+      }
+
+      // Dark theme special-cases
+      html[data-theme="dark"] & {
+        &.rendered_html:not(:has(table.dataframe)),
+        // ipywidgets
+        .widget-subarea {
+          @include cell-output-background;
+        }
+      }
+    }
   }
 }