@@ -57,6 +57,26 @@ function applyTheme(layout) {
5757 return layout ;
5858}
5959
60+ // ── Shared Plotly config ─────────────────────────────────────────────────────
61+ // Adds a "Download as SVG" button to every plot's modebar alongside the
62+ // default "Download plot as a png" camera button.
63+ const PLOTLY_CONFIG = {
64+ modeBarButtonsToAdd : [
65+ {
66+ name : 'Download plot as SVG' ,
67+ icon : {
68+ // Simple download-arrow icon (Material Design "file_download")
69+ width : 24 ,
70+ height : 24 ,
71+ path : 'M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z' ,
72+ } ,
73+ click : function ( gd ) {
74+ Plotly . downloadImage ( gd , { format : 'svg' , filename : gd . id || 'dandi-plot' } ) ;
75+ } ,
76+ } ,
77+ ] ,
78+ } ;
79+
6080/**
6181 * Reads the saved theme preference from localStorage (defaulting to dark),
6282 * applies it to the <html> element and updates IS_DARK_MODE.
@@ -1238,7 +1258,7 @@ function load_over_time_plot(dandiset_id) {
12381258 layout . title . text = "Usage per week" ;
12391259 }
12401260
1241- Plotly . newPlot ( plot_element_id , plot_info , layout ) ;
1261+ Plotly . newPlot ( plot_element_id , plot_info , layout , PLOTLY_CONFIG ) ;
12421262 attach_legend_tooltips ( plot_element_id , ASSET_TYPE_DESCRIPTIONS ) ;
12431263
12441264 // Table: show total bytes per time bin (sum across all asset types)
@@ -1366,7 +1386,7 @@ function load_over_time_plot(dandiset_id) {
13661386 layout . barmode = "stack" ;
13671387 layout . legend = { title : { text : "Dandiset" } } ;
13681388
1369- Plotly . newPlot ( plot_element_id , plot_info , layout ) ;
1389+ Plotly . newPlot ( plot_element_id , plot_info , layout , PLOTLY_CONFIG ) ;
13701390
13711391 // Render archive table view even in grouped mode
13721392 const per_bin_titles = {
@@ -1459,7 +1479,7 @@ function load_over_time_plot(dandiset_id) {
14591479
14601480 const layout = build_over_time_layout ( dates ) ;
14611481
1462- Plotly . newPlot ( plot_element_id , plot_info , layout ) ;
1482+ Plotly . newPlot ( plot_element_id , plot_info , layout , PLOTLY_CONFIG ) ;
14631483
14641484 // Render table view (sortable by column header click; default: bytes descending)
14651485 const date_col_labels = {
@@ -1580,7 +1600,7 @@ function load_dandiset_histogram() {
15801600 } ,
15811601 } ) ;
15821602
1583- Plotly . newPlot ( plot_element_id , plot_data , layout ) ;
1603+ Plotly . newPlot ( plot_element_id , plot_data , layout , PLOTLY_CONFIG ) ;
15841604
15851605 // Render table view (sortable by column header click; default: bytes descending)
15861606 render_sortable_table ( "histogram_table" , "" , [
@@ -1667,7 +1687,7 @@ function load_per_asset_histogram(by_asset_summary_tsv_url) {
16671687 } ,
16681688 } ) ;
16691689
1670- Plotly . newPlot ( plot_element_id , plot_data , layout ) ;
1690+ Plotly . newPlot ( plot_element_id , plot_data , layout , PLOTLY_CONFIG ) ;
16711691
16721692 // Render table view (sortable by column header click; default: bytes descending)
16731693 render_sortable_table ( "histogram_table" , "Usage per asset" , [
@@ -2038,7 +2058,7 @@ function load_geographic_heatmap(dandiset_id) {
20382058 } ,
20392059 } ) ;
20402060
2041- Plotly . newPlot ( plot_element_id , plot_info , layout ) ;
2061+ Plotly . newPlot ( plot_element_id , plot_info , layout , PLOTLY_CONFIG ) ;
20422062 } )
20432063 . catch ( ( error ) => {
20442064 console . error ( "Error:" , error ) ;
@@ -2205,7 +2225,7 @@ function load_geographic_choropleth(dandiset_id, plot_element_id, by_region_summ
22052225 ] ,
22062226 } ) ;
22072227
2208- Plotly . newPlot ( plot_element_id , plot_info , layout ) . then ( ( ) => {
2228+ Plotly . newPlot ( plot_element_id , plot_info , layout , PLOTLY_CONFIG ) . then ( ( ) => {
22092229 const el = document . getElementById ( plot_element_id ) ;
22102230 if ( el && el . _fullLayout && el . _fullLayout . map && el . _fullLayout . map . _subplot ) {
22112231 const map = el . _fullLayout . map . _subplot . map ;
0 commit comments