@@ -582,10 +582,27 @@ pub fn build_density_graph<'a>(
582582 } ;
583583
584584 if should_render_individual_events {
585+ // Render all individual events
585586 for ( time, num_events) in chunk. num_events_cumulative_per_unique_time ( timeline) {
586- data. add_chunk_point ( time, num_events as usize ) ;
587+ data. add_chunk_point ( time, num_events as f32 ) ;
588+ }
589+ } else if config. max_sampled_events_per_chunk > 0 {
590+ let events = chunk. num_events_cumulative_per_unique_time ( timeline) ;
591+
592+ if events. len ( ) > config. max_sampled_events_per_chunk {
593+ // If there's more rows than the configured max, we sample events to get a fast, good enough density estimate.
594+ data. add_uniform_sample_from_chunk (
595+ & events,
596+ config. max_sampled_events_per_chunk ,
597+ ) ;
598+ } else {
599+ // No need to sample, we can use all events.
600+ for ( time, num_events) in events {
601+ data. add_chunk_point ( time, num_events as f32 ) ;
602+ }
587603 }
588604 } else {
605+ // Fall back to uniform distribution across the entire time range
589606 data. add_chunk_range ( time_range, num_events_in_chunk) ;
590607 }
591608 }
@@ -604,6 +621,10 @@ pub struct DensityGraphBuilderConfig {
604621
605622 /// If an unsorted chunk has fewer events than this we show its individual events.
606623 pub max_events_in_unsorted_chunk : u64 ,
624+
625+ /// When a chunk is too large to render all events, uniformly sample this many events
626+ /// to create a good enough density estimate instead.
627+ pub max_sampled_events_per_chunk : usize ,
607628}
608629
609630impl DensityGraphBuilderConfig {
@@ -612,6 +633,7 @@ impl DensityGraphBuilderConfig {
612633 max_total_chunk_events : 0 ,
613634 max_events_in_unsorted_chunk : 0 ,
614635 max_events_in_sorted_chunk : 0 ,
636+ max_sampled_events_per_chunk : 0 ,
615637 } ;
616638
617639 /// All sorted chunks will be rendered as individual events,
@@ -620,13 +642,15 @@ impl DensityGraphBuilderConfig {
620642 max_total_chunk_events : u64:: MAX ,
621643 max_events_in_unsorted_chunk : 0 ,
622644 max_events_in_sorted_chunk : u64:: MAX ,
645+ max_sampled_events_per_chunk : 0 ,
623646 } ;
624647
625648 /// All chunks will be rendered as individual events.
626649 pub const ALWAYS_SPLIT_ALL_CHUNKS : Self = Self {
627650 max_total_chunk_events : u64:: MAX ,
628651 max_events_in_unsorted_chunk : u64:: MAX ,
629652 max_events_in_sorted_chunk : u64:: MAX ,
653+ max_sampled_events_per_chunk : 0 ,
630654 } ;
631655}
632656
@@ -649,6 +673,10 @@ impl Default for DensityGraphBuilderConfig {
649673
650674 // Processing unsorted events is about 20% slower than sorted events.
651675 max_events_in_unsorted_chunk : 8_000 ,
676+
677+ // When chunks are too large to render all events, sample this many events uniformly
678+ // to create a good enough density estimate.
679+ max_sampled_events_per_chunk : 8_000 ,
652680 }
653681 }
654682}
@@ -709,12 +737,32 @@ impl<'a> DensityGraphBuilder<'a> {
709737 }
710738 }
711739
712- fn add_chunk_point ( & mut self , time : TimeInt , num_events : usize ) {
740+ /// Uniformly sample events using the given sample size.
741+ ///
742+ /// Each sampled event's count is reweighted to preserve the total density.
743+ fn add_uniform_sample_from_chunk ( & mut self , events : & [ ( TimeInt , u64 ) ] , sample_size : usize ) {
744+ re_tracing:: profile_function!( ) ;
745+
746+ let step = events. len ( ) as f32 / sample_size as f32 ;
747+
748+ for i in 0 ..sample_size {
749+ let idx = ( i as f32 * step) as usize ;
750+ // This means we might miss the last event if rounding down, but that's acceptable.
751+ if let Some ( & ( time, count) ) = events. get ( idx) {
752+ // Reweight the count to preserve total density
753+ let weighted_count = count as f32 * step;
754+
755+ self . add_chunk_point ( time, weighted_count) ;
756+ }
757+ }
758+ }
759+
760+ fn add_chunk_point ( & mut self , time : TimeInt , weight : f32 ) {
713761 let Some ( x) = self . time_ranges_ui . x_from_time_f32 ( time. into ( ) ) else {
714762 return ;
715763 } ;
716764
717- self . density_graph . add_point ( x, num_events as _ ) ;
765+ self . density_graph . add_point ( x, weight ) ;
718766
719767 if let Some ( pointer_pos) = self . pointer_pos {
720768 let is_hovered = {
0 commit comments