@@ -1120,13 +1120,20 @@ function load_over_time_plot(dandiset_id) {
11201120 // ── Grouped mode: overlay asset types ────────────────────────────────────
11211121 if ( OVER_TIME_GROUP_BY === "asset_type" ) {
11221122 const tsv_url = `${ BASE_TSV_URL } /${ dandiset_id } /by_asset_type_per_week.tsv` ;
1123+ const archive_tsv_url = `${ BASE_TSV_URL } /${ dandiset_id } /by_day.tsv` ;
11231124
1124- return fetch ( tsv_url )
1125+ const asset_type_promise = fetch ( tsv_url )
11251126 . then ( ( r ) => {
11261127 if ( ! r . ok ) throw new Error ( `HTTP ${ r . status } ` ) ;
11271128 return r . text ( ) ;
1128- } )
1129- . then ( ( text ) => {
1129+ } ) ;
1130+ const archive_promise = fetch ( archive_tsv_url )
1131+ . then ( ( r ) => r . ok ? r . text ( ) : Promise . reject ( new Error ( `HTTP ${ r . status } ` ) ) )
1132+ . then ( ( text ) => parse_by_day_tsv ( text ) )
1133+ . catch ( ( ) => null ) ;
1134+
1135+ return Promise . all ( [ asset_type_promise , archive_promise ] )
1136+ . then ( ( [ text , archive_data ] ) => {
11301137 const { dates : raw_dates , asset_types, series_map } = parse_by_asset_type_per_week_tsv ( text ) ;
11311138
11321139 // Data is weekly; treat "daily" aggregation as weekly since no finer data exists
@@ -1159,9 +1166,42 @@ function load_over_time_plot(dandiset_id) {
11591166 } ;
11601167 } ) ;
11611168
1169+ // Build an "Other" series: archive total minus the sum of all asset types
1170+ if ( archive_data ) {
1171+ const archive_agg = aggregate_by_timebin ( archive_data . dates , archive_data . bytes , effective_aggregation ) ;
1172+ const archive_plot_data = USE_CUMULATIVE
1173+ ? make_cumulative ( archive_agg . bytes_sent )
1174+ : archive_agg . bytes_sent ;
1175+ // Build per-date lookup for the sum of all asset-type series (already cumulative if USE_CUMULATIVE)
1176+ const series_by_date = new Map ( ) ;
1177+ for ( const series of plot_info ) {
1178+ series . x . forEach ( ( date , idx ) => {
1179+ series_by_date . set ( date , ( series_by_date . get ( date ) || 0 ) + series . y [ idx ] ) ;
1180+ } ) ;
1181+ }
1182+ const other_y = archive_agg . dates . map ( ( date , i ) => {
1183+ const asset_type_total = series_by_date . get ( date ) || 0 ;
1184+ return Math . max ( 0 , archive_plot_data [ i ] - asset_type_total ) ;
1185+ } ) ;
1186+ const other_human_readable = other_y . map ( ( b ) => format_bytes ( b ) ) ;
1187+ plot_info . push ( {
1188+ type : "bar" ,
1189+ name : "Other" ,
1190+ x : archive_agg . dates ,
1191+ y : other_y ,
1192+ text : archive_agg . dates . map ( ( date , idx ) =>
1193+ `Other<br>${ bin_label_prefix } ${ date } <br>${ other_human_readable [ idx ] } `
1194+ ) ,
1195+ textposition : "none" ,
1196+ hoverinfo : "text" ,
1197+ marker : { color : "rgba(150,150,150,0.7)" } ,
1198+ } ) ;
1199+ all_dates_for_layout . push ( ...archive_agg . dates ) ;
1200+ }
1201+
11621202 const unique_dates = [ ...new Set ( all_dates_for_layout ) ] . sort ( ) ;
11631203 const layout = build_over_time_layout ( unique_dates ) ;
1164- layout . barmode = "overlay " ;
1204+ layout . barmode = "stack " ;
11651205 layout . showlegend = true ;
11661206 layout . legend = { title : { text : "Asset type" } } ;
11671207
@@ -1260,10 +1300,42 @@ function load_over_time_plot(dandiset_id) {
12601300 } ;
12611301 } ) ;
12621302
1303+ // Build an "Other" series: archive total minus the sum of all top-N dandisets
1304+ if ( archive_data ) {
1305+ const archive_agg = aggregate_by_timebin ( archive_data . dates , archive_data . bytes , TIME_AGGREGATION ) ;
1306+ const archive_plot_data = USE_CUMULATIVE
1307+ ? make_cumulative ( archive_agg . bytes_sent )
1308+ : archive_agg . bytes_sent ;
1309+ // Build per-date lookup for the sum of top-N series (already cumulative if USE_CUMULATIVE)
1310+ const series_by_date = new Map ( ) ;
1311+ for ( const series of valid_series ) {
1312+ series . dates . forEach ( ( date , idx ) => {
1313+ series_by_date . set ( date , ( series_by_date . get ( date ) || 0 ) + series . plot_data [ idx ] ) ;
1314+ } ) ;
1315+ }
1316+ const other_y = archive_agg . dates . map ( ( date , i ) => {
1317+ const top_n_total = series_by_date . get ( date ) || 0 ;
1318+ return Math . max ( 0 , archive_plot_data [ i ] - top_n_total ) ;
1319+ } ) ;
1320+ const other_human_readable = other_y . map ( ( b ) => format_bytes ( b ) ) ;
1321+ plot_info . push ( {
1322+ type : "bar" ,
1323+ name : "Other" ,
1324+ x : archive_agg . dates ,
1325+ y : other_y ,
1326+ text : archive_agg . dates . map ( ( date , idx ) =>
1327+ `Other<br>${ bin_label_prefix } ${ date } <br>${ other_human_readable [ idx ] } `
1328+ ) ,
1329+ textposition : "none" ,
1330+ hoverinfo : "text" ,
1331+ marker : { color : "rgba(150,150,150,0.7)" } ,
1332+ } ) ;
1333+ }
1334+
12631335 // Collect all dates across series for range-break calculation
12641336 const all_series_dates = valid_series . flatMap ( ( s ) => s . dates ) ;
12651337 const layout = build_over_time_layout ( all_series_dates ) ;
1266- layout . barmode = "overlay " ;
1338+ layout . barmode = "stack " ;
12671339 layout . legend = { title : { text : "Dandiset" } } ;
12681340
12691341 Plotly . newPlot ( plot_element_id , plot_info , layout ) ;
0 commit comments