HTML Visualizations in Documentation#

This page describes the complete procedure for generating interactive plot_data HTML files and embedding them in the Sphinx documentation.

All HTML files live in doc/source/_static/html/plot_data_view_examples/ and are embedded in .rst pages via <iframe> directives.

Complete Procedure#

Step 1: Build a PrimitiveGroup in Python#

from dessia_drawing.core import Drawing
from drawing_tools.config.default_language_configs import DEFAULT_FRENCH_CONFIG, DEFAULT_ENGLISH_CONFIG
from drawing_tools.sheet.featured_sheet import FeaturedSheet
from drawing_tools.view.featured_view import FeaturedView

drawing = Drawing.from_json("data/json/my_drawing.json")
fs = FeaturedSheet(drawing.sheets[0], language_configs=[DEFAULT_FRENCH_CONFIG, DEFAULT_ENGLISH_CONFIG])

# Any @plot_data_view method:
primitive_group = fs.plot_data_balloons()
# or view-level:
# primitive_group = fs.views[2].plot_data_balloons()

Step 2: Serialize to JSON and write into the HTML template#

import json
from pathlib import Path

data_str = json.dumps(primitive_group.to_dict())
output = Path("doc/source/_static/html/plot_data_view_examples/my_visualization.html")

TEMPLATE = '''<!DOCTYPE html>
<html lang="en">
    <style>
    #buttons {
      display: flex;
      column-gap: 3rem;
      row-gap: 0.5em;
      flex-wrap: wrap;
      margin-bottom: 0.5rem;
      margin-top: 1rem;
      margin: 0 auto;
    }
    </style>
    <head>
        <script src="https://documentation.dessia.io/plot_data/_static/htmls/plot-data.js"></script>
    </head>
    <div id="buttons"></div>
    <div id="app">
        <canvas id="canvas" style="border: 1px solid black; margin: auto;"></canvas>
        <script type="text/javascript">
            const [width, height] = PlotData.computeCanvasSize("#buttons");
            const data = DATA_PLACEHOLDER;
            const canvas = document.getElementById("canvas");
            const plot_data = new PlotData.Draw(data, width, height, 0, 0, canvas.id);
            plot_data.setCanvas(canvas.id);
        </script>
    </div>
</html>'''

output.write_text(TEMPLATE.replace("DATA_PLACEHOLDER", data_str))

Step 3: Embed in the .rst page#

.. raw:: html

   <iframe src="../_static/html/plot_data_view_examples/my_visualization.html"
           width="100%" height="600px" frameborder="0"></iframe>

The src path is relative to the .rst file. For files in doc/source/guides/, use ../_static/html/....

Step 4: Verify in a browser#

Open the HTML file directly in a browser and check:

  • The visualization renders (not blank)

  • There are no toolbar buttons (Zoom Box, Cluster, Reset, Merge, etc.)

  • View page source: <script src=...> points to https://documentation.dessia.io/...

Critical Rules#

The HTML template above has three properties that must not be changed:

  1. CDN script URL — The <script src> must be https://documentation.dessia.io/plot_data/_static/htmls/plot-data.js. Never use a local .venv/ path — it works on your machine but breaks in deployed docs.

  2. Empty ``<div id=”buttons”></div>`` — This div must exist in the HTML even though it is empty. The JavaScript call PlotData.computeCanvasSize("#buttons") uses this element to compute canvas dimensions. Without it, the canvas has zero dimensions and the visualization is a blank white page.

  3. No toolbar buttons — The generated HTML must not contain <button> elements, <input type="range" class="slider">, <div id="sub_button">, or <hr> separators. These are toolbar controls that plot_data adds when using .plot(filepath=...), but they clutter the embedded iframe and are not needed for documentation.

Why Not Use .plot(filepath=...) Directly#

The PrimitiveGroup.plot(filepath=...) method generates a complete HTML file, but it has two problems for documentation:

  1. Local script path — It writes <script src=/home/.../plot-data.js> pointing to your virtualenv. This breaks in deployed docs.

  2. Toolbar buttons — It includes Zoom Box, Cluster slider, Reset view, Merge points, Show points, Change Disposition, Log Scale, Resize, and Apply on rubberband buttons. These clutter the iframe.

If you must use .plot(filepath=...), you need to clean the output afterwards. Here is the exact procedure to fix an HTML file generated by .plot(filepath=...):

Fixing an HTML File Generated by .plot(filepath=...)#

The following Python code fixes a single HTML file. It applies all necessary regex substitutions to remove toolbar elements and fix the CDN path. Run it on any file or directory:

import re
from pathlib import Path

CDN_SCRIPT = '<script src="https://documentation.dessia.io/plot_data/_static/htmls/plot-data.js"></script>'

def fix_html_for_docs(filepath: Path) -> None:
    content = filepath.read_text(encoding="utf-8")
    original = content

    # 1. Replace local script path with CDN (handles both quoted and unquoted src attributes)
    content = re.sub(
        r"<script src=[\"']?[^\"'>]*plot-data\.js[^>]*></script>",
        CDN_SCRIPT,
        content,
    )

    # 2. Remove all <button> elements (Zoom, Reset, Merge, etc.)
    content = re.sub(r"\s*<button[^>]*>.*?</button>\s*", "", content, flags=re.DOTALL)

    # 3. Remove slider and styled inputs
    content = re.sub(r"\s*Cluster:\s*<input[^>]*class=\"slider\"[^>]*/>\s*", "", content)
    content = re.sub(r"\s*<input[^>]*class=\"styled\"[^>]*/>\s*", "", content)
    content = re.sub(r"\s*<input[^>]*type=\"button\"[^>]*/>\s*", "", content)

    # 4. Remove <div id="buttons">...</div> wrapper (may be empty after button removal)
    content = re.sub(r'\s*<div id="buttons">[\s]*</div>', "", content, flags=re.DOTALL)
    content = re.sub(r'\s*<div id="buttons">.*?</div>\s*(?:</div>\s*)*', "\n", content, flags=re.DOTALL)

    # 5. Remove orphaned <div id="sub_button"> wrappers
    content = re.sub(r'\s*<div id="sub_button">\s*</div>', "", content)
    content = re.sub(r'\s*<div id="sub_button">[\s\S]*?</div>', "", content)

    # 6. Remove <hr> separator between toolbar and canvas
    content = re.sub(r"\s*<hr[^>]*/?>", "", content)

    # 7. Remove CSS rules for slider and sub_button (keep only #buttons rule)
    content = re.sub(r"\s*\.slider\s*\{[^}]*\}\s*", "\n", content)
    content = re.sub(r"\s*\.slider:hover\s*\{[^}]*\}\s*", "\n", content)
    content = re.sub(r"\s*\.slider::-moz-range-thumb\s*\{[^}]*\}\s*", "\n", content)
    content = re.sub(r"\s*#sub_button\s*\{[^}]*\}\s*", "\n", content)

    # 8. Clean up excessive blank lines
    content = re.sub(r"\n{3,}", "\n\n", content)

    if content != original:
        filepath.write_text(content, encoding="utf-8")

# Fix a single file:
fix_html_for_docs(Path("doc/source/_static/html/plot_data_view_examples/my_file.html"))

# Or fix all HTML files in the directory:
for html_file in Path("doc/source/_static/html/plot_data_view_examples").rglob("*.html"):
    fix_html_for_docs(html_file)

What the Toolbar Looks Like (to Recognize It)#

When .plot(filepath=...) generates an HTML file, it includes a toolbar at the top of the page with these elements that must be removed:

  • <button>Switch Point Merge</button>

  • <button>Zoom Box</button>, <button>Zoom+</button>, <button>Zoom-</button>

  • Cluster: <input type="range" class="slider" .../>

  • <button>Reset clusters</button>

  • <button>Reset view</button>, <button>Show points</button>

  • <button>Change Disposition</button>, <button>Show / Hide Axes</button>

  • <button>Log Scale</button>, <button>Resize</button>

  • <input class="styled" type="button" value="Apply on rubberband" .../>

  • <hr style="border-top: 2px;"/> (separator between toolbar and canvas)

These are wrapped in <div id="buttons"><div id="sub_button">...</div>...</div>. The CSS rules .slider, .slider:hover, .slider::-moz-range-thumb, and #sub_button are also added by .plot() and should be removed.

The #buttons CSS rule must be kept — it is needed by computeCanvasSize.

Auditing Existing HTML Files#

To check if any HTML file in the doc still has unwanted toolbar buttons:

for f in doc/source/_static/html/plot_data_view_examples/*.html; do
    n=$(grep -c "<button" "$f" 2>/dev/null)
    [ "$n" -gt 0 ] && echo "BAD ($n buttons): $(basename $f)"
done

If any files show up as BAD, apply the fix procedure above.