11import glob
2+ import json
23from argparse import ArgumentParser
34from pathlib import Path
45from shutil import copyfile
78import pandas as pd
89import plotly .express as px
910import plotly .graph_objects as go
11+ from plotly .utils import PlotlyJSONEncoder
1012from scipy .stats import gaussian_kde
1113from table_tag_utils import wrap_tags
1214
@@ -225,7 +227,7 @@ def _fmt_int(v):
225227 x_field : x_label ,
226228 },
227229 color_discrete_map = MODALITY_COLOR_MAP ,
228- title = "Dataset Landscape " ,
230+ title = "" ,
229231 category_orders = {
230232 "modality_label" : [
231233 label
@@ -263,7 +265,7 @@ def _fmt_int(v):
263265 size = 14 ,
264266 ),
265267 title = dict (
266- text = "Dataset Landscape " ,
268+ text = "" ,
267269 x = 0.01 ,
268270 xanchor = "left" ,
269271 y = 0.98 ,
@@ -684,7 +686,7 @@ def main(source_dir: str, target_dir: str):
684686 ),
685687 margin = dict (l = 120 , r = 40 , t = 80 , b = 80 ),
686688 title = dict (
687- text = "Participant Distribution by Modality " ,
689+ text = "" ,
688690 x = 0.01 ,
689691 xanchor = "left" ,
690692 y = 0.98 ,
@@ -694,25 +696,26 @@ def main(source_dir: str, target_dir: str):
694696 )
695697 # Add CSS and loading indicator for immediate proper sizing
696698 kde_height = max (650 , 150 * len (order ))
697- html_content = fig_kde .to_html (
698- full_html = False ,
699- include_plotlyjs = False ,
700- div_id = "dataset-kde-modalities" ,
701- config = {
702- "responsive" : True ,
703- "displaylogo" : False ,
704- "modeBarButtonsToRemove" : ["lasso2d" , "select2d" ],
705- "toImageButtonOptions" : {
706- "format" : "png" ,
707- "filename" : "participant_kde" ,
708- "height" : {kde_height },
709- "width" : 1200 ,
710- "scale" : 2 ,
711- },
699+ plot_config = {
700+ "responsive" : True ,
701+ "displaylogo" : False ,
702+ "modeBarButtonsToRemove" : ["lasso2d" , "select2d" ],
703+ "toImageButtonOptions" : {
704+ "format" : "png" ,
705+ "filename" : "participant_kde" ,
706+ "height" : kde_height ,
707+ "width" : 1200 ,
708+ "scale" : 2 ,
712709 },
710+ }
711+ fig_spec = fig_kde .to_plotly_json ()
712+ data_json = json .dumps (fig_spec .get ("data" , []), cls = PlotlyJSONEncoder )
713+ layout_json = json .dumps (
714+ fig_spec .get ("layout" , {}), cls = PlotlyJSONEncoder
713715 )
716+ config_json = json .dumps (plot_config , cls = PlotlyJSONEncoder )
714717
715- # Wrap with styling to ensure proper initial sizing
718+ # Wrap with styling to ensure proper initial sizing and defer Plotly rendering
716719 styled_html = f"""
717720<style>
718721#dataset-kde-modalities {{
@@ -721,8 +724,9 @@ def main(source_dir: str, target_dir: str):
721724 height: { kde_height } px !important;
722725 min-height: { kde_height } px;
723726 margin: 0 auto;
727+ display: none;
724728}}
725- #dataset-kde-modalities .plotly-graph-div {{
729+ #dataset-kde-modalities.plotly-graph-div {{
726730 width: 100% !important;
727731 height: 100% !important;
728732}}
@@ -736,28 +740,60 @@ def main(source_dir: str, target_dir: str):
736740}}
737741</style>
738742<div class="kde-loading" id="kde-loading">Loading participant distribution...</div>
739- { html_content }
743+ <div id="dataset-kde-modalities" class="plotly-graph-div"></div>
740744<script>
741- // Hide loading indicator once plot is rendered
742- document.addEventListener('DOMContentLoaded', function() {{
743- const loading = document.getElementById('kde-loading');
744- const plot = document.getElementById('dataset-kde-modalities');
745- if (loading && plot) {{
746- loading.style.display = 'none';
747- plot.style.display = 'block';
748-
749- // Add click event to points for navigation
750- plot.on('plotly_click', function(data) {{
751- if (data.points && data.points[0] && data.points[0].customdata) {{
752- const url = data.points[0].customdata[1]; // dataset_url
753- if (url) {{
754- const resolved = new URL(url, window.location.href);
755- window.open(resolved.href, '_self');
756- }}
757- }}
758- }});
745+ (function() {{
746+ const TARGET_ID = 'dataset-kde-modalities';
747+ const FIG_DATA = { data_json } ;
748+ const FIG_LAYOUT = { layout_json } ;
749+ const FIG_CONFIG = { config_json } ;
750+
751+ function onReady(callback) {{
752+ if (document.readyState === 'loading') {{
753+ document.addEventListener('DOMContentLoaded', callback, {{ once: true }});
754+ }} else {{
755+ callback();
759756 }}
760- }});
757+ }}
758+
759+ function renderPlot() {{
760+ const container = document.getElementById(TARGET_ID);
761+ if (!container) {{
762+ return;
763+ }}
764+
765+ const draw = () => {{
766+ if (!window.Plotly) {{
767+ window.requestAnimationFrame(draw);
768+ return;
769+ }}
770+
771+ window.Plotly.newPlot(TARGET_ID, FIG_DATA, FIG_LAYOUT, FIG_CONFIG).then((plot) => {{
772+ const loading = document.getElementById('kde-loading');
773+ if (loading) {{
774+ loading.style.display = 'none';
775+ }}
776+ container.style.display = 'block';
777+
778+ plot.on('plotly_click', (event) => {{
779+ const point = event.points && event.points[0];
780+ if (!point || !point.customdata) {{
781+ return;
782+ }}
783+ const url = point.customdata[1];
784+ if (url) {{
785+ const resolved = new URL(url, window.location.href);
786+ window.open(resolved.href, '_self');
787+ }}
788+ }});
789+ }});
790+ }};
791+
792+ draw();
793+ }}
794+
795+ onReady(renderPlot);
796+ }})();
761797</script>
762798"""
763799
0 commit comments