diff --git a/docs/_ext/notoc.py b/docs/_ext/notoc.py
new file mode 100644
index 000000000..ccdb8e474
--- /dev/null
+++ b/docs/_ext/notoc.py
@@ -0,0 +1,21 @@
+from docutils.parsers.rst import Directive
+from docutils import nodes
+
+
+class NoTocDirective(Directive):
+ has_content = False
+
+ def run(self):
+ # Create a raw HTML node to add the no-right-toc class to body
+ html = ''
+ return [nodes.raw("", html, format="html")]
+
+
+def setup(app):
+ app.add_directive("notoc", NoTocDirective)
+
+ return {
+ "version": "0.1",
+ "parallel_read_safe": True,
+ "parallel_write_safe": True,
+ }
diff --git a/docs/_static/custom.css b/docs/_static/custom.css
new file mode 100644
index 000000000..d6a77e4f2
--- /dev/null
+++ b/docs/_static/custom.css
@@ -0,0 +1,188 @@
+.grid-item-card .card-img-top {
+ height: 100%;
+ object-fit: cover;
+ width: 100%;
+ background-color: slategrey;
+}
+
+/* Make all cards with this class use flexbox for vertical layout */
+.card-with-bottom-text {
+ display: flex !important;
+ flex-direction: column !important;
+ height: 100% !important;
+}
+
+/* Style the card content areas */
+.card-with-bottom-text .sd-card-body {
+ display: flex !important;
+ flex-direction: column !important;
+ flex-grow: 1 !important;
+}
+
+/* Make images not grow or shrink */
+.card-with-bottom-text img {
+ flex-shrink: 0 !important;
+ margin-bottom: 0.5rem !important;
+}
+
+/* Push the last paragraph to the bottom */
+.card-with-bottom-text .sd-card-body > p:last-child {
+ margin-top: auto !important;
+ padding-top: 0.5rem !important;
+ text-align: center !important;
+}
+
+.img-container img {
+ object-fit: cover;
+ width: 100%;
+ height: 100%;
+}
+
+.right-toc {
+ position: fixed;
+ top: 90px;
+ right: 20px;
+ width: 280px;
+ font-size: 0.9em;
+ max-height: calc(100vh - 150px);
+ background-color: #f8f9fa;
+ z-index: 100;
+ border-radius: 6px;
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
+ transition: all 0.3s ease;
+ border-left: 3px solid #2980b9;
+}
+
+.right-toc-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 15px;
+ border-bottom: 1px solid #e1e4e5;
+}
+
+.right-toc-title {
+ font-weight: 600;
+ font-size: 1.1em;
+ color: #2980b9;
+}
+
+.right-toc-buttons {
+ display: flex;
+ align-items: center;
+}
+
+.right-toc-toggle-btn {
+ background: none;
+ border: none;
+ color: #2980b9;
+ font-size: 16px;
+ cursor: pointer;
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 3px;
+ padding: 0;
+ transition: background-color 0.2s;
+}
+
+.right-toc-toggle-btn:hover {
+ background-color: rgba(41, 128, 185, 0.1);
+}
+
+.right-toc-content {
+ padding: 15px 15px 15px 20px;
+ overflow-y: auto;
+ max-height: calc(100vh - 200px);
+}
+
+.right-toc-list {
+ list-style-type: none;
+ padding-left: 0;
+ margin: 0;
+}
+
+.right-toc-link {
+ display: block;
+ padding: 5px 0;
+ text-decoration: none;
+ color: #404040;
+ border-radius: 4px;
+ transition: all 0.2s ease;
+ margin-bottom: 3px;
+}
+
+.right-toc-link:hover {
+ background-color: rgba(41, 128, 185, 0.1);
+ padding-left: 5px;
+ color: #2980b9;
+}
+
+.right-toc-level-h1 {
+ font-weight: 600;
+ font-size: 1em;
+}
+
+.right-toc-level-h2 {
+ padding-left: 1.2em;
+ font-size: 0.95em;
+}
+
+.right-toc-level-h3 {
+ padding-left: 2.4em;
+ font-size: 0.9em;
+ color: #606060;
+}
+
+/* Active TOC item highlighting */
+.right-toc-link.active {
+ background-color: rgba(41, 128, 185, 0.15);
+ color: #2980b9;
+ font-weight: 500;
+ padding-left: 5px;
+}
+
+/* Collapsed state */
+.right-toc-collapsed {
+ width: auto;
+ border-left-width: 0;
+}
+
+.right-toc-collapsed .right-toc-header {
+ border-bottom: none;
+ padding: 8px 12px;
+}
+
+/* Scrollbar styling */
+.right-toc-content::-webkit-scrollbar {
+ width: 5px;
+}
+
+.right-toc-content::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 10px;
+}
+
+.right-toc-content::-webkit-scrollbar-thumb {
+ background: #cdcdcd;
+ border-radius: 10px;
+}
+
+.right-toc-content::-webkit-scrollbar-thumb:hover {
+ background: #9e9e9e;
+}
+
+/* Responsive adjustments */
+@media screen and (max-width: 1200px) {
+ .right-toc {
+ width: 230px;
+ }
+}
+
+@media screen and (max-width: 992px) {
+ .right-toc {
+ display: none; /* Hide on smaller screens */
+ }
+}
diff --git a/docs/_static/custom.js b/docs/_static/custom.js
new file mode 100644
index 000000000..ef54f6847
--- /dev/null
+++ b/docs/_static/custom.js
@@ -0,0 +1,175 @@
+document.addEventListener("DOMContentLoaded", function () {
+ // Check if current page has opted out of the TOC
+ if (document.body.classList.contains("no-right-toc")) {
+ return;
+ }
+
+ const content = document.querySelector(".rst-content");
+ if (!content) return;
+
+ // Find all headers in the main content
+ const headers = Array.from(
+ content.querySelectorAll("h1:not(.document-title), h2, h3"),
+ ).filter((header) => !header.classList.contains("no-toc"));
+
+ // Only create TOC if there are headers
+ if (headers.length === 0) return;
+
+ // Create TOC container
+ const toc = document.createElement("div");
+ toc.className = "right-toc";
+ toc.innerHTML =
+ '
" +
+ '';
+
+ const tocList = toc.querySelector(".right-toc-list");
+ const tocContent = toc.querySelector(".right-toc-content");
+ const tocToggleBtn = toc.querySelector(
+ ".right-toc-toggle-btn",
+ );
+
+ // Set up the toggle button
+ tocToggleBtn.addEventListener("click", function () {
+ if (tocContent.style.display === "none") {
+ tocContent.style.display = "block";
+ tocToggleBtn.textContent = "−";
+ toc.classList.remove("right-toc-collapsed");
+ localStorage.setItem("tocVisible", "true");
+ } else {
+ tocContent.style.display = "none";
+ tocToggleBtn.textContent = "+";
+ toc.classList.add("right-toc-collapsed");
+ localStorage.setItem("tocVisible", "false");
+ }
+ });
+
+ // Check saved state
+ if (localStorage.getItem("tocVisible") === "false") {
+ tocContent.style.display = "none";
+ tocToggleBtn.textContent = "+";
+ toc.classList.add("right-toc-collapsed");
+ }
+
+ // Track used IDs to avoid duplicates
+ const usedIds = new Set();
+
+ // Get all existing IDs in the document
+ document.querySelectorAll("[id]").forEach((el) => {
+ usedIds.add(el.id);
+ });
+
+ // Generate unique IDs for headers that need them
+ headers.forEach((header, index) => {
+ // If header already has a unique ID, use that
+ if (header.id && !usedIds.has(header.id)) {
+ usedIds.add(header.id);
+ return;
+ }
+
+ // Create a slug from the header text
+ let headerText = header.textContent || "";
+
+ // Clean the text (remove icons and special characters)
+ headerText = headerText.replace(/\s*\uf0c1\s*$/, "");
+ headerText = headerText.replace(/\s*[¶§#†‡]\s*$/, "");
+ headerText = headerText.trim();
+
+ let slug = headerText
+ .toLowerCase()
+ .replace(/[^\w\s-]/g, "")
+ .replace(/\s+/g, "-")
+ .replace(/--+/g, "-")
+ .trim();
+
+ // Make sure slug is not empty
+ if (!slug) {
+ slug = "section";
+ }
+
+ // Ensure the ID is unique
+ let uniqueId = slug;
+ let counter = 1;
+
+ while (usedIds.has(uniqueId)) {
+ uniqueId = `${slug}-${counter}`;
+ counter++;
+ }
+
+ // Set the unique ID and add to our tracking set
+ header.id = uniqueId;
+ usedIds.add(uniqueId);
+ });
+
+ // Add entries for each header
+ headers.forEach((header) => {
+ const item = document.createElement("li");
+ const link = document.createElement("a");
+
+ link.href = "#" + header.id;
+
+ // Get clean text without icons
+ let headerText = header.textContent || "";
+ headerText = headerText.replace(/\s*\uf0c1\s*$/, "");
+ headerText = headerText.replace(/\s*[¶§#†‡]\s*$/, "");
+
+ link.textContent = headerText.trim();
+ link.className =
+ "right-toc-link right-toc-level-" +
+ header.tagName.toLowerCase();
+
+ item.appendChild(link);
+ tocList.appendChild(item);
+ });
+
+ // Add TOC to page
+ document.body.appendChild(toc);
+
+ // Add active link highlighting
+ const tocLinks = document.querySelectorAll(".right-toc-link");
+ const headerElements = Array.from(headers);
+
+ if (tocLinks.length > 0 && headerElements.length > 0) {
+ // Highlight the current section on scroll
+ window.addEventListener(
+ "scroll",
+ debounce(function () {
+ let currentSection = null;
+ let smallestDistanceFromTop = Infinity;
+
+ headerElements.forEach((header) => {
+ const distance = Math.abs(
+ header.getBoundingClientRect().top,
+ );
+ if (distance < smallestDistanceFromTop) {
+ smallestDistanceFromTop = distance;
+ currentSection = header.id;
+ }
+ });
+
+ tocLinks.forEach((link) => {
+ link.classList.remove("active");
+ if (
+ link.getAttribute("href") === `#${currentSection}`
+ ) {
+ link.classList.add("active");
+ }
+ });
+ }, 100),
+ );
+ }
+});
+
+// Debounce function to limit scroll event firing
+function debounce(func, wait) {
+ let timeout;
+ return function () {
+ const context = this;
+ const args = arguments;
+ clearTimeout(timeout);
+ timeout = setTimeout(() => func.apply(context, args), wait);
+ };
+}
diff --git a/docs/_static/example_plots/cartesian_example.png b/docs/_static/example_plots/cartesian_example.png
new file mode 100644
index 000000000..eb6eb3ce8
Binary files /dev/null and b/docs/_static/example_plots/cartesian_example.png differ
diff --git a/docs/_static/example_plots/colorbars_legends_example.png b/docs/_static/example_plots/colorbars_legends_example.png
new file mode 100644
index 000000000..99ebb5986
Binary files /dev/null and b/docs/_static/example_plots/colorbars_legends_example.png differ
diff --git a/docs/_static/example_plots/colormaps_example.png b/docs/_static/example_plots/colormaps_example.png
new file mode 100644
index 000000000..b8f741d02
Binary files /dev/null and b/docs/_static/example_plots/colormaps_example.png differ
diff --git a/docs/_static/example_plots/panels_example.png b/docs/_static/example_plots/panels_example.png
new file mode 100644
index 000000000..ab2b6b6be
Binary files /dev/null and b/docs/_static/example_plots/panels_example.png differ
diff --git a/docs/_static/example_plots/projection_example.png b/docs/_static/example_plots/projection_example.png
new file mode 100644
index 000000000..7f63b2ce4
Binary files /dev/null and b/docs/_static/example_plots/projection_example.png differ
diff --git a/docs/_static/example_plots/subplot_example.png b/docs/_static/example_plots/subplot_example.png
new file mode 100644
index 000000000..a40d169f2
Binary files /dev/null and b/docs/_static/example_plots/subplot_example.png differ
diff --git a/docs/conf.py b/docs/conf.py
index cad4db09c..467583e9f 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -131,6 +131,7 @@ def __getattr__(self, name):
extensions = [
"matplotlib.sphinxext.plot_directive", # see: https://matplotlib.org/sampledoc/extensions.html # noqa: E501
"sphinx.ext.autodoc", # include documentation from docstrings
+ "sphinx_design",
"sphinx.ext.doctest", # >>> examples
"sphinx.ext.extlinks", # for :pr:, :issue:, :commit:
"sphinx.ext.autosectionlabel", # use :ref:`Heading` for any heading
@@ -144,6 +145,7 @@ def __getattr__(self, name):
"sphinx_automodapi.automodapi", # fork of automodapi
"sphinx_rtd_light_dark", # use custom theme
"sphinx_copybutton", # add copy button to code
+ "_ext.notoc",
"nbsphinx", # parse rst books
]
@@ -288,6 +290,8 @@ def __getattr__(self, name):
# Add jupytext support to nbsphinx
nbsphinx_custom_formats = {".py": ["jupytext.reads", {"fmt": "py:percent"}]}
+nbsphinx_execute = "auto"
+
# The name of the Pygments (syntax highlighting) style to use.
# The light-dark theme toggler overloads this, but set default anyway
pygments_style = "none"
@@ -311,6 +315,10 @@ def __getattr__(self, name):
"collapse_navigation": True,
"navigation_depth": 4,
"prev_next_buttons_location": "bottom", # top and bottom
+ "includehidden": True,
+ "titles_only": True,
+ "display_toc": True,
+ "sticky_navigation": True,
}
# Add any paths that contain custom static files (such as style sheets) here,
@@ -338,6 +346,13 @@ def __getattr__(self, name):
htmlhelp_basename = "ultraplotdoc"
+html_css_files = [
+ "custom.css",
+]
+html_js_files = [
+ "custom.js",
+]
+
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
diff --git a/docs/index.rst b/docs/index.rst
index bd8004e10..0926e1cff 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,21 +1,122 @@
-..
- UltraPlot documentation master file, created by
- sphinx-quickstart on Wed Feb 20 01:31:20 2019.
- You can adapt this file completely to your liking, but
- it should at least contain the root `toctree` directive.
+.. notoc::
+.. image:: _static/logo_long.png
+ :align: center
-=======
-UltraPlot
-=======
+**UltraPlot** is a succinct wrapper around `matplotlib `__
+for creating **beautiful, publication-quality graphics** with ease.
-A succinct `matplotlib `__ wrapper
-for making beautiful, publication-quality graphics. This project
-is `published on GitHub `__ and can
-be cited using its `Zenodo DOI `__.
+🚀 **Key Features** | Create More, Code Less
+###################
+✔ **Simplified Subplot Management** – Create multi-panel plots effortlessly.
+🎨 **Smart Aesthetics** – Optimized colormaps, fonts, and styles out of the box.
+
+📊 **Versatile Plot Types** – Cartesian plots, insets, colormaps, and more.
+
+📌 **Get Started** → :doc:`Installation guide ` | :doc:`Why UltraPlot? ` | :doc:`Usage `
+
+--------------------------------------
+
+**📖 User Guide**
+#################
+A preview of what UltraPlot can do. For more see the sidebar!
+
+.. grid:: 3
+ :gutter: 2
+
+ .. grid-item-card::
+ :link: subplots.html
+ :shadow: md
+ :class-card: card-with-bottom-text
+
+ **Subplots & Layouts**
+ ^^^
+
+ .. image:: _static/example_plots/subplot_example.png
+ :align: center
+
+ Create complex multi-panel layouts effortlessly.
+
+ .. grid-item-card::
+ :link: cartesian.html
+ :shadow: md
+ :class-card: card-with-bottom-text
+
+ **Cartesian Plots**
+ ^^^
+
+ .. image:: _static/example_plots/cartesian_example.png
+ :align: center
+
+ .. container:: bottom-aligned-text
+
+ Easily generate clean, well-formatted plots.
+
+ .. grid-item-card::
+ :link: projections.html
+ :shadow: md
+ :class-card: card-with-bottom-text
+
+ **Projections & Maps**
+ ^^^
+
+ .. image:: _static/example_plots/projection_example.png
+ :align: center
+
+ .. container:: bottom-aligned-text
+ Built-in support for projections and geographic plots.
+
+ .. grid-item-card::
+ :link: colorbars_legends.html
+ :shadow: md
+ :class-card: card-with-bottom-text
+
+ **Colorbars & Legends**
+ ^^^
+
+ .. image:: _static/example_plots/colorbars_legends_example.png
+ :align: center
+
+ Customize legends and colorbars with ease.
+
+ .. grid-item-card::
+ :link: insets_panels.html
+ :shadow: md
+ :class-card: card-with-bottom-text
+
+ **Insets & Panels**
+ ^^^
+
+ .. image:: _static/example_plots/panels_example.png
+ :align: center
+
+ Add inset plots and panel-based layouts.
+
+ .. grid-item-card::
+ :link: colormaps.html
+ :shadow: md
+ :class-card: card-with-bottom-text
+
+ **Colormaps & Cycles**
+ ^^^
+
+ .. image:: _static/example_plots/colormaps_example.png
+ :align: center
+
+ Use prebuilt colormaps and define your own color cycles.
+
+
+**📚 Reference & More**
+#######################
+For more details, check the full :doc:`User guide ` and :doc:`API Reference `.
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`glossary`
.. toctree::
:maxdepth: 1
:caption: Getting Started
+ :hidden:
install
why
@@ -24,6 +125,7 @@ be cited using its `Zenodo DOI `__.
.. toctree::
:maxdepth: 1
:caption: User Guide
+ :hidden:
basics
subplots
@@ -43,16 +145,10 @@ be cited using its `Zenodo DOI `__.
.. toctree::
:maxdepth: 1
:caption: Reference
+ :hidden:
api
external-links
whats_new
contributing
authors
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`glossary`
diff --git a/environment.yml b/environment.yml
index 214a9e873..3674d5563 100644
--- a/environment.yml
+++ b/environment.yml
@@ -24,5 +24,6 @@ dependencies:
- sphinx-rtd-theme
- basemap >=1.4.1
- pre-commit
+ - sphinx-design
- pip:
- git+https://github.com/ultraplot/UltraTheme.git