@@ -519,49 +519,45 @@ def critical_difference_diagram(
519519 )
520520
521521 # Sets of points under the same crossbar
522- crossbar_sets = [bar for bar in _find_maximal_cliques (adj_matrix ) if len (bar ) > 1 ]
523-
524- if crossbar_sets : # If there are any crossbars to plot
525- crossbar_min_max = [ # Will be used to check if two crossbars intersect
526- ranks .reindex (bar ).agg (["min" , "max" ])
527- for bar in crossbar_sets
522+ crossbar_ranks = (
523+ ranks .reindex (bar ).sort_values ().values
524+ for bar in _find_maximal_cliques (adj_matrix )
525+ if len (bar ) > 1
526+ )
527+ # Try to fit wider crossbars first
528+ crossbar_ranks = list (sorted (crossbar_ranks , key = lambda x : x [0 ] - x [- 1 ]))
529+
530+ # If any crossbar is found, plot them
531+ if crossbar_ranks :
532+ # Create stacking of crossbars: for each level, try to fit the crossbar,
533+ # so that it does not intersect with any other in the level. If it does not
534+ # fit in any level, create a new level for it.
535+ crossbar_levels : list [list [np .ndarray ]] = []
536+ for bar_i in crossbar_ranks :
537+ for bars_in_level in crossbar_levels :
538+ if all (
539+ (bar_i [- 1 ] < bar_j [0 ]) or (bar_i [0 ] > bar_j [- 1 ])
540+ for bar_j in bars_in_level
541+ ):
542+ bars_in_level .append (bar_i )
543+ break
544+ else :
545+ crossbar_levels .append ([bar_i ]) # Create a new level
546+
547+ # Plot crossbars
548+ # We could plot a single line segment between min and max. However,
549+ # adding a separate segment between each pair enables showing a
550+ # marker over each elbow, e.g. crossbar_props={'marker': 'o'}.
551+ crossbars = [
552+ [ax .plot (bar , [- i ] * len (bar ), ** crossbar_props ) for bar in level ]
553+ for i , level in enumerate (crossbar_levels )
528554 ]
529555
530- # Create an adjacency matrix of the crossbars, where 1 means that the two
531- # crossbars do not intersect, meaning that they can be plotted on the same
532- # level.
533- n_bars = len (crossbar_sets )
534- on_same_level = DataFrame (True , index = range (n_bars ), columns = range (n_bars ))
535-
536- for (i , bar_i ), (j , bar_j ) in combinations (enumerate (crossbar_min_max ), 2 ):
537- on_same_level .loc [i , j ] = on_same_level .loc [j , i ] = (
538- (bar_i ["max" ] < bar_j ["min" ]) or (bar_i ["min" ] > bar_j ["max" ])
539- )
540-
541- # The levels are the maximal cliques of the crossbar adjacency matrix.
542- crossbar_levels = _find_maximal_cliques (on_same_level )
543-
544- # Plot the crossbars in each level
545- for level , bars_in_level in enumerate (crossbar_levels ):
546- plotted_bars_in_level = []
547- for bar_index in bars_in_level :
548- bar = crossbar_sets [bar_index ]
549- plotted_bar , * _ = ax .plot (
550- # We could plot a single line segment between min and max. However,
551- # adding a separate segment between each pair enables showing a
552- # marker over each elbow, e.g. crossbar_props={'marker': 'o'}.
553- [ranks [i ] for i in bar ],
554- [- level - 1 ] * len (bar ),
555- ** crossbar_props ,
556- )
557- plotted_bars_in_level .append (plotted_bar )
558- crossbars .append (plotted_bars_in_level )
559-
560- lowest_crossbar_ypos = - len (crossbars )
556+ elbow_start_y = - len (crossbars )
561557
562558 def plot_items (points , xpos , label_fmt , color_palette , label_props ):
563559 """Plot each marker + elbow + label."""
564- ypos = lowest_crossbar_ypos - 1
560+ ypos = elbow_start_y
565561 for idx , (label , rank ) in enumerate (points .items ()):
566562 if not color_palette or len (color_palette ) == 0 :
567563 elbow , * _ = ax .plot (
0 commit comments