Skip to content

Commit a2424a4

Browse files
committed
deploy: 6fa6256
1 parent 46383a9 commit a2424a4

6 files changed

Lines changed: 495 additions & 90 deletions

File tree

.doctrees/environment.pickle

0 Bytes
Binary file not shown.

_modules/index.html

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -498,14 +498,41 @@ <h1>All modules for which code is available</h1>
498498
let modal = null;
499499
let modalContent = null;
500500
let previousScrollOffset = [window.scrollX, window.scrollY];
501+
let _lazyObserver = null;
502+
let _renderQueue = Promise.resolve();
503+
504+
// Apply d3 zoom to each SVG. Idempotent: an SVG already wrapped is skipped,
505+
// so this is safe to call both for the initial batch and for diagrams that
506+
// render lazily once they become visible.
507+
const addZoomToSvgs = (svgs) => {
508+
svgs.each(function() {
509+
if (this.getAttribute('data-zoom-applied') === 'true') return;
510+
var svg = d3.select(this);
511+
svg.html("<g class='wrapper'>" + svg.html() + "</g>");
512+
var inner = svg.select("g");
513+
var zoom = d3.zoom().on("zoom", function(event) {
514+
inner.attr("transform", event.transform);
515+
});
516+
svg.call(zoom);
517+
this.setAttribute('data-zoom-applied', 'true');
518+
});
519+
};
501520

502521
const runMermaid = async (rerun) => {
503522
console.log("Running mermaid diagrams, rerun =", rerun);
504523
// clear all existing mermaid charts
505524
let all_mermaids = document.querySelectorAll(".mermaid");
506525

507526
if (rerun) {
527+
// Disconnect any previous lazy rendering observer
528+
if (_lazyObserver) {
529+
_lazyObserver.disconnect();
530+
_lazyObserver = null;
531+
}
532+
508533
all_mermaids.forEach((el) => {
534+
el.removeAttribute('data-mermaid-deferred');
535+
el.removeAttribute('data-mermaid-render-failed');
509536
if(!el.hasAttribute("data-original-code")) {
510537
// store original code
511538
// console.log(`Storing original code for first run: `, el.innerHTML);
@@ -522,35 +549,89 @@ <h1>All modules for which code is available</h1>
522549
el.setAttribute('data-original-code', el.innerHTML);
523550
}
524551
});
525-
await mermaid.run();
552+
553+
// Separate visible and hidden elements. Mermaid's layout engine measures
554+
// text dimensions via the DOM, which fails for elements hidden by a parent
555+
// (e.g., Reveal.js non-active slides, sphinx-design unopened tabs),
556+
// producing broken SVGs. Render visible elements now, defer hidden ones.
557+
const visible = [];
558+
const hidden = [];
559+
all_mermaids.forEach((el) => {
560+
// offsetParent is null for display:none ancestors.
561+
// getClientRects().length > 0 catches position:fixed elements
562+
// (which also have null offsetParent but are still visible).
563+
if (el.offsetParent !== null || el.getClientRects().length > 0) {
564+
visible.push(el);
565+
} else {
566+
hidden.push(el);
567+
el.setAttribute('data-mermaid-deferred', 'true');
568+
}
569+
});
570+
571+
if (visible.length > 0) {
572+
try {
573+
await mermaid.run({ nodes: visible });
574+
} catch (e) {
575+
console.error("Mermaid rendering failed:", e);
576+
}
577+
}
578+
579+
// Lazily render hidden elements when they become visible.
580+
// Uses a promise chain (_renderQueue) to serialize rendering,
581+
// since IntersectionObserver may report multiple entries at once
582+
// and mermaid.run() is not safe to call concurrently.
583+
if (hidden.length > 0) {
584+
if (typeof IntersectionObserver === 'undefined') {
585+
console.warn("IntersectionObserver not available; hidden mermaid diagrams will not render.");
586+
} else {
587+
_renderQueue = Promise.resolve();
588+
_lazyObserver = new IntersectionObserver((entries) => {
589+
for (const entry of entries) {
590+
if (entry.isIntersecting && entry.target.getAttribute("data-processed") !== "true") {
591+
_lazyObserver.unobserve(entry.target);
592+
const el = entry.target;
593+
_renderQueue = _renderQueue.then(async () => {
594+
try {
595+
await mermaid.run({ nodes: [el] });
596+
el.removeAttribute('data-mermaid-deferred');
597+
// Apply zoom to the now-rendered diagram, matching
598+
// the decoration visible diagrams receive.
599+
if ("True" === "True") {
600+
addZoomToSvgs(d3.selectAll(".mermaid svg"));
601+
}
602+
} catch (e) {
603+
console.error("Mermaid deferred rendering failed:", e);
604+
el.removeAttribute('data-mermaid-deferred');
605+
el.setAttribute('data-mermaid-render-failed', 'true');
606+
}
607+
});
608+
}
609+
}
610+
});
611+
hidden.forEach((el) => _lazyObserver.observe(el));
612+
}
613+
}
526614
}
527615

528616
all_mermaids = document.querySelectorAll(".mermaid");
529-
const mermaids_processed = document.querySelectorAll(".mermaid[data-processed='true']");
617+
// Exclude deferred (hidden) and render-failed elements from completion checks
618+
const mermaids_active = document.querySelectorAll(".mermaid:not([data-mermaid-deferred]):not([data-mermaid-render-failed])");
619+
const mermaids_processed = document.querySelectorAll(".mermaid:not([data-mermaid-deferred]):not([data-mermaid-render-failed])[data-processed='true']");
530620

531621
if ("True" === "True") {
532-
const mermaids_to_add_zoom = -1 === -1 ? all_mermaids.length : -1;
622+
const mermaids_to_add_zoom = -1 === -1 ? mermaids_active.length : -1;
533623
if(mermaids_to_add_zoom > 0) {
534624
var svgs = d3.selectAll(".mermaid svg");
535-
if(all_mermaids.length !== mermaids_processed.length) {
536-
setTimeout(() => runMermaid(false), 200);
537-
return;
538-
} else if(svgs.size() !== mermaids_to_add_zoom) {
625+
// Wait until every active (visible) diagram has rendered. Deferred
626+
// diagrams are excluded, so a fixed d3_node_count that counts a
627+
// hidden diagram no longer loops forever waiting for its SVG.
628+
if(mermaids_active.length !== mermaids_processed.length) {
539629
setTimeout(() => runMermaid(false), 200);
540630
return;
541-
} else {
542-
svgs.each(function() {
543-
var svg = d3.select(this);
544-
svg.html("<g class='wrapper'>" + svg.html() + "</g>");
545-
var inner = svg.select("g");
546-
var zoom = d3.zoom().on("zoom", function(event) {
547-
inner.attr("transform", event.transform);
548-
});
549-
svg.call(zoom);
550-
});
551631
}
632+
addZoomToSvgs(svgs);
552633
}
553-
} else if(all_mermaids.length !== mermaids_processed.length) {
634+
} else if(mermaids_active.length !== mermaids_processed.length) {
554635
// Wait for mermaid to process all diagrams
555636
setTimeout(() => runMermaid(false), 200);
556637
return;

_modules/python_template_cpp.html

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -524,14 +524,41 @@ <h1>Source code for python_template_cpp</h1><div class="highlight"><pre>
524524
let modal = null;
525525
let modalContent = null;
526526
let previousScrollOffset = [window.scrollX, window.scrollY];
527+
let _lazyObserver = null;
528+
let _renderQueue = Promise.resolve();
529+
530+
// Apply d3 zoom to each SVG. Idempotent: an SVG already wrapped is skipped,
531+
// so this is safe to call both for the initial batch and for diagrams that
532+
// render lazily once they become visible.
533+
const addZoomToSvgs = (svgs) => {
534+
svgs.each(function() {
535+
if (this.getAttribute('data-zoom-applied') === 'true') return;
536+
var svg = d3.select(this);
537+
svg.html("<g class='wrapper'>" + svg.html() + "</g>");
538+
var inner = svg.select("g");
539+
var zoom = d3.zoom().on("zoom", function(event) {
540+
inner.attr("transform", event.transform);
541+
});
542+
svg.call(zoom);
543+
this.setAttribute('data-zoom-applied', 'true');
544+
});
545+
};
527546

528547
const runMermaid = async (rerun) => {
529548
console.log("Running mermaid diagrams, rerun =", rerun);
530549
// clear all existing mermaid charts
531550
let all_mermaids = document.querySelectorAll(".mermaid");
532551

533552
if (rerun) {
553+
// Disconnect any previous lazy rendering observer
554+
if (_lazyObserver) {
555+
_lazyObserver.disconnect();
556+
_lazyObserver = null;
557+
}
558+
534559
all_mermaids.forEach((el) => {
560+
el.removeAttribute('data-mermaid-deferred');
561+
el.removeAttribute('data-mermaid-render-failed');
535562
if(!el.hasAttribute("data-original-code")) {
536563
// store original code
537564
// console.log(`Storing original code for first run: `, el.innerHTML);
@@ -548,35 +575,89 @@ <h1>Source code for python_template_cpp</h1><div class="highlight"><pre>
548575
el.setAttribute('data-original-code', el.innerHTML);
549576
}
550577
});
551-
await mermaid.run();
578+
579+
// Separate visible and hidden elements. Mermaid's layout engine measures
580+
// text dimensions via the DOM, which fails for elements hidden by a parent
581+
// (e.g., Reveal.js non-active slides, sphinx-design unopened tabs),
582+
// producing broken SVGs. Render visible elements now, defer hidden ones.
583+
const visible = [];
584+
const hidden = [];
585+
all_mermaids.forEach((el) => {
586+
// offsetParent is null for display:none ancestors.
587+
// getClientRects().length > 0 catches position:fixed elements
588+
// (which also have null offsetParent but are still visible).
589+
if (el.offsetParent !== null || el.getClientRects().length > 0) {
590+
visible.push(el);
591+
} else {
592+
hidden.push(el);
593+
el.setAttribute('data-mermaid-deferred', 'true');
594+
}
595+
});
596+
597+
if (visible.length > 0) {
598+
try {
599+
await mermaid.run({ nodes: visible });
600+
} catch (e) {
601+
console.error("Mermaid rendering failed:", e);
602+
}
603+
}
604+
605+
// Lazily render hidden elements when they become visible.
606+
// Uses a promise chain (_renderQueue) to serialize rendering,
607+
// since IntersectionObserver may report multiple entries at once
608+
// and mermaid.run() is not safe to call concurrently.
609+
if (hidden.length > 0) {
610+
if (typeof IntersectionObserver === 'undefined') {
611+
console.warn("IntersectionObserver not available; hidden mermaid diagrams will not render.");
612+
} else {
613+
_renderQueue = Promise.resolve();
614+
_lazyObserver = new IntersectionObserver((entries) => {
615+
for (const entry of entries) {
616+
if (entry.isIntersecting && entry.target.getAttribute("data-processed") !== "true") {
617+
_lazyObserver.unobserve(entry.target);
618+
const el = entry.target;
619+
_renderQueue = _renderQueue.then(async () => {
620+
try {
621+
await mermaid.run({ nodes: [el] });
622+
el.removeAttribute('data-mermaid-deferred');
623+
// Apply zoom to the now-rendered diagram, matching
624+
// the decoration visible diagrams receive.
625+
if ("True" === "True") {
626+
addZoomToSvgs(d3.selectAll(".mermaid svg"));
627+
}
628+
} catch (e) {
629+
console.error("Mermaid deferred rendering failed:", e);
630+
el.removeAttribute('data-mermaid-deferred');
631+
el.setAttribute('data-mermaid-render-failed', 'true');
632+
}
633+
});
634+
}
635+
}
636+
});
637+
hidden.forEach((el) => _lazyObserver.observe(el));
638+
}
639+
}
552640
}
553641

554642
all_mermaids = document.querySelectorAll(".mermaid");
555-
const mermaids_processed = document.querySelectorAll(".mermaid[data-processed='true']");
643+
// Exclude deferred (hidden) and render-failed elements from completion checks
644+
const mermaids_active = document.querySelectorAll(".mermaid:not([data-mermaid-deferred]):not([data-mermaid-render-failed])");
645+
const mermaids_processed = document.querySelectorAll(".mermaid:not([data-mermaid-deferred]):not([data-mermaid-render-failed])[data-processed='true']");
556646

557647
if ("True" === "True") {
558-
const mermaids_to_add_zoom = -1 === -1 ? all_mermaids.length : -1;
648+
const mermaids_to_add_zoom = -1 === -1 ? mermaids_active.length : -1;
559649
if(mermaids_to_add_zoom > 0) {
560650
var svgs = d3.selectAll(".mermaid svg");
561-
if(all_mermaids.length !== mermaids_processed.length) {
562-
setTimeout(() => runMermaid(false), 200);
563-
return;
564-
} else if(svgs.size() !== mermaids_to_add_zoom) {
651+
// Wait until every active (visible) diagram has rendered. Deferred
652+
// diagrams are excluded, so a fixed d3_node_count that counts a
653+
// hidden diagram no longer loops forever waiting for its SVG.
654+
if(mermaids_active.length !== mermaids_processed.length) {
565655
setTimeout(() => runMermaid(false), 200);
566656
return;
567-
} else {
568-
svgs.each(function() {
569-
var svg = d3.select(this);
570-
svg.html("<g class='wrapper'>" + svg.html() + "</g>");
571-
var inner = svg.select("g");
572-
var zoom = d3.zoom().on("zoom", function(event) {
573-
inner.attr("transform", event.transform);
574-
});
575-
svg.call(zoom);
576-
});
577657
}
658+
addZoomToSvgs(svgs);
578659
}
579-
} else if(all_mermaids.length !== mermaids_processed.length) {
660+
} else if(mermaids_active.length !== mermaids_processed.length) {
580661
// Wait for mermaid to process all diagrams
581662
setTimeout(() => runMermaid(false), 200);
582663
return;

0 commit comments

Comments
 (0)