@@ -914,6 +914,15 @@ function aggregate_by_timebin(dates, bytes_sent, aggregation) {
914914 } ;
915915}
916916
917+ // Abbreviated descriptions shown as tooltip text on legend items when the
918+ // over-time plot is grouped by asset type.
919+ const ASSET_TYPE_DESCRIPTIONS = {
920+ Neurophysiology : "NWB files" ,
921+ Microscopy : "OME-Zarr, NIfTI, TIFF" ,
922+ Video : "AVI, MKV, MP4, MOV, WMV" ,
923+ Miscellaneous : "TXT, TSV, JSON, code, etc." ,
924+ } ;
925+
917926// Categorical colour palette for the "group by Dandisets" overlay bars.
918927// Colours include 0.7 alpha so overlapping bars remain visible.
919928const DANDISET_BAR_COLORS = [
@@ -973,6 +982,39 @@ function parse_by_asset_type_per_week_tsv(text) {
973982 return { dates, asset_types, series_map } ;
974983}
975984
985+ /**
986+ * Adds SVG <title> tooltip elements to Plotly legend items whose display name
987+ * appears in `label_to_tooltip`. Attaches to the plotly_afterplot event so
988+ * tooltips survive redraws (theme switches, resizes, trace toggles, etc.).
989+ *
990+ * @param {string } plot_element_id - ID of the Plotly graph div.
991+ * @param {Object } label_to_tooltip - Map of legend label → tooltip string.
992+ */
993+ function attach_legend_tooltips ( plot_element_id , label_to_tooltip ) {
994+ const el = document . getElementById ( plot_element_id ) ;
995+ if ( ! el ) return ;
996+
997+ function inject_titles ( ) {
998+ el . querySelectorAll ( ".legendtext" ) . forEach ( ( text_el ) => {
999+ const desc = label_to_tooltip [ text_el . textContent ] ;
1000+ if ( ! desc ) return ;
1001+ const group = text_el . closest ( ".traces" ) ;
1002+ if ( ! group ) return ;
1003+ // Replace any stale title so re-renders always have the correct text.
1004+ const existing = group . querySelector ( "title" ) ;
1005+ if ( existing ) existing . remove ( ) ;
1006+ const title_el = document . createElementNS ( "http://www.w3.org/2000/svg" , "title" ) ;
1007+ title_el . textContent = desc ;
1008+ group . appendChild ( title_el ) ;
1009+ } ) ;
1010+ }
1011+
1012+ inject_titles ( ) ;
1013+ // `el.on()` is Plotly's own event-emitter API (added to the graph div by
1014+ // Plotly.newPlot); it is the documented way to subscribe to plotly_* events.
1015+ el . on ( "plotly_afterplot" , inject_titles ) ;
1016+ }
1017+
9761018/**
9771019 * Builds the shared layout options used by both single-series and grouped
9781020 * over-time plots.
@@ -1129,6 +1171,7 @@ function load_over_time_plot(dandiset_id) {
11291171 }
11301172
11311173 Plotly . newPlot ( plot_element_id , plot_info , layout ) ;
1174+ attach_legend_tooltips ( plot_element_id , ASSET_TYPE_DESCRIPTIONS ) ;
11321175
11331176 // Table: show total bytes per time bin (sum across all asset types)
11341177 const total_bytes = raw_dates . map ( ( _ , i ) =>
0 commit comments