@@ -40,21 +40,20 @@ class Circos:
4040
4141    def  __init__ (
4242        self ,
43-         sectors : Mapping [str , int  |  float ],
43+         sectors : Mapping [str , int  |  float   |   tuple [ float ,  float ] ],
4444        start : float  =  0 ,
4545        end : float  =  360 ,
4646        * ,
4747        space : float  |  list [float ] =  0 ,
4848        endspace : bool  =  True ,
49-         sector2start_pos : Mapping [str , int  |  float ] |  None  =  None ,
5049        sector2clockwise : dict [str , bool ] |  None  =  None ,
5150        show_axis_for_debug : bool  =  False ,
5251    ):
5352        """ 
5453        Parameters 
5554        ---------- 
56-         sectors : Mapping[str, int | float] 
57-             Sector name & size dict 
55+         sectors : Mapping[str, int | float | tuple[float, float] ] 
56+             Sector name & size (or range)  dict 
5857        start : float, optional 
5958            Plot start degree (`-360 <= start < end <= 360`) 
6059        end : float, optional 
@@ -63,14 +62,11 @@ def __init__(
6362            Space degree(s) between sector 
6463        endspace : bool, optional 
6564            If True, insert space after the end sector 
66-         sector2start_pos : Mapping[str, int | float] | None, optional 
67-             Sector name & start position dict. By default, `start_pos=0`. 
6865        sector2clockwise : dict[str, bool] | None, optional 
6966            Sector name & clockwise bool dict. By default, `clockwise=True`. 
7067        show_axis_for_debug : bool, optional 
7168            Show axis for position check debugging (Developer option) 
7269        """ 
73-         sector2start_pos  =  {} if  sector2start_pos  is  None  else  sector2start_pos 
7470        sector2clockwise  =  {} if  sector2clockwise  is  None  else  sector2clockwise 
7571
7672        # Check start-end degree range 
@@ -100,19 +96,21 @@ def __init__(
10096                """ 
10197            )[1 :- 1 ]
10298            raise  ValueError (err_msg )
103-         sector_total_size  =  sum (sectors .values ())
99+ 
100+         sector2range  =  self ._to_sector2range (sectors )
101+         sector_total_size  =  sum ([max (r ) -  min (r ) for  r  in  sector2range .values ()])
104102
105103        rad_pos  =  math .radians (start )
106104        self ._sectors : list [Sector ] =  []
107-         for  idx , (sector_name , sector_size ) in  enumerate (sectors .items ()):
105+         for  idx , (sector_name , sector_range ) in  enumerate (sector2range .items ()):
106+             sector_size  =  max (sector_range ) -  min (sector_range )
108107            sector_size_ratio  =  sector_size  /  sector_total_size 
109108            deg_size  =  whole_deg_size_without_space  *  sector_size_ratio 
110109            rad_size  =  math .radians (deg_size )
111110            rad_lim  =  (rad_pos , rad_pos  +  rad_size )
112111            rad_pos  +=  rad_size  +  math .radians (space_list [idx ])
113-             start_pos  =  sector2start_pos .get (sector_name , 0 )
114112            clockwise  =  sector2clockwise .get (sector_name , True )
115-             sector  =  Sector (sector_name , sector_size , rad_lim ,  start_pos , clockwise )
113+             sector  =  Sector (sector_name , sector_range , rad_lim , clockwise )
116114            self ._sectors .append (sector )
117115
118116        self ._deg_lim  =  (start , end )
@@ -180,6 +178,7 @@ def radar_chart(
180178        table : str  |  Path  |  pd .DataFrame  |  RadarTable ,
181179        * ,
182180        r_lim : tuple [float , float ] =  (0 , 100 ),
181+         vmin : float  =  0 ,
183182        vmax : float  =  100 ,
184183        fill : bool  =  True ,
185184        marker_size : int  =  0 ,
@@ -203,6 +202,8 @@ def radar_chart(
203202            Table file or Table dataframe or RadarTable instance 
204203        r_lim : tuple[float, float], optional 
205204            Radar chart radius limit region (0 - 100) 
205+         vmin : float, optional 
206+             Min value 
206207        vmax : float, optional 
207208            Max value 
208209        fill : bool, optional 
@@ -244,6 +245,10 @@ def radar_chart(
244245        circos : Circos 
245246            Circos instance initialized for radar chart 
246247        """ 
248+         if  not  vmin  <  vmax :
249+             raise  ValueError (f"vmax must be larger than vmin ({ vmin = }  , { vmax = }  )" )
250+         size  =  vmax  -  vmin 
251+ 
247252        # Setup default properties 
248253        grid_line_kws  =  {} if  grid_line_kws  is  None  else  deepcopy (grid_line_kws )
249254        for  k , v  in  dict (color = "grey" , ls = "dashed" , lw = 0.5 ).items ():
@@ -269,11 +274,12 @@ def radar_chart(
269274            if  not  0  <  grid_interval_ratio  <=  1.0 :
270275                raise  ValueError (f"{ grid_interval_ratio = }   is invalid." )
271276            # Plot horizontal grid line & label 
272-             stop , step  =  vmax  +  (vmax  /  1000 ), vmax  *  grid_interval_ratio 
273-             for  v  in  np .arange (0 , stop , step ):
274-                 track .line (x , [v ] *  len (x ), vmax = vmax , arc = circular , ** grid_line_kws )
277+             stop , step  =  vmax  +  (size  /  1000 ), size  *  grid_interval_ratio 
278+             for  v  in  np .arange (vmin , stop , step ):
279+                 y  =  [v ] *  len (x )
280+                 track .line (x , y , vmin = vmin , vmax = vmax , arc = circular , ** grid_line_kws )
275281                if  show_grid_label :
276-                     r  =  track ._y_to_r (v , 0 , vmax )
282+                     r  =  track ._y_to_r (v , vmin , vmax )
277283                    # Format grid label 
278284                    if  grid_label_formatter :
279285                        text  =  grid_label_formatter (v )
@@ -283,7 +289,7 @@ def radar_chart(
283289                    track .text (text , 0 , r , ** grid_label_kws )
284290            # Plot vertical grid line 
285291            for  p  in  x [:- 1 ]:
286-                 track .line ([p , p ], [0 , vmax ], vmax = vmax , ** grid_line_kws )
292+                 track .line ([p , p ], [vmin , vmax ],  vmin = vmin , vmax = vmax , ** grid_line_kws )
287293
288294        # Plot radar charts 
289295        if  isinstance (cmap , str ):
@@ -296,15 +302,16 @@ def radar_chart(
296302            line_kws  =  line_kws_handler (row_name ) if  line_kws_handler  else  {}
297303            line_kws .setdefault ("lw" , 1.0 )
298304            line_kws .setdefault ("label" , row_name )
299-             track .line (x , y , vmax = vmax , arc = False , color = color , ** line_kws )
305+             track .line (x , y , vmin = vmin ,  vmax = vmax , arc = False , color = color , ** line_kws )
300306            if  marker_size  >  0 :
301307                marker_kws  =  marker_kws_handler (row_name ) if  marker_kws_handler  else  {}
302308                marker_kws .setdefault ("marker" , "o" )
303309                marker_kws .setdefault ("zorder" , 2 )
304310                marker_kws .update (s = marker_size ** 2 )
305-                 track .scatter (x , y , vmax = vmax , color = color , ** marker_kws )
311+                 track .scatter (x , y , vmin = vmin ,  vmax = vmax , color = color , ** marker_kws )
306312            if  fill :
307-                 track .fill_between (x , y , vmax = vmax , arc = False , color = color , alpha = 0.5 )
313+                 fill_kws  =  dict (arc = False , color = color , alpha = 0.5 )
314+                 track .fill_between (x , y , y2 = vmin , vmin = vmin , vmax = vmax , ** fill_kws )  # type:ignore 
308315
309316        # Plot column names 
310317        for  idx , col_name  in  enumerate (radar_table .col_names ):
@@ -577,15 +584,13 @@ def initialize_from_bed(
577584            Circos instance initialized from BED file 
578585        """ 
579586        records  =  Bed (bed_file ).records 
580-         sectors  =  {rec .chr : rec .size  for  rec  in  records }
581-         sector2start_pos  =  {rec .chr : rec .start  for  rec  in  records }
587+         sectors  =  {rec .chr : (rec .start , rec .end ) for  rec  in  records }
582588        return  Circos (
583589            sectors ,
584590            start ,
585591            end ,
586592            space = space ,
587593            endspace = endspace ,
588-             sector2start_pos = sector2start_pos ,
589594            sector2clockwise = sector2clockwise ,
590595        )
591596
@@ -1098,6 +1103,23 @@ def _check_degree_range(self, start: float, end: float) -> None:
10981103            err_msg  =  f"'end - start' must be less than { max_deg }   ({ start = }  , { end = }  )" 
10991104            raise  ValueError (err_msg )
11001105
1106+     def  _to_sector2range (
1107+         self ,
1108+         sectors : Mapping [str , int  |  float  |  tuple [float , float ]],
1109+     ) ->  dict [str , tuple [float , float ]]:
1110+         """Convert sectors to sector2range""" 
1111+         sector2range : dict [str , tuple [float , float ]] =  {}
1112+         for  name , value  in  sectors .items ():
1113+             if  isinstance (value , (tuple , list )):
1114+                 sector_start , sector_end  =  value 
1115+                 if  not  sector_start  <  sector_end :
1116+                     err_msg  =  f"{ sector_end = }   must be larger than { sector_start = }  ." 
1117+                     raise  ValueError (err_msg )
1118+                 sector2range [name ] =  (sector_start , sector_end )
1119+             else :
1120+                 sector2range [name ] =  (0 , value )
1121+         return  sector2range 
1122+ 
11011123    def  _initialize_figure (
11021124        self ,
11031125        figsize : tuple [float , float ] =  (8 , 8 ),
0 commit comments