|
13 | 13 | from .utils import _parse_date |
14 | 14 |
|
15 | 15 |
|
| 16 | +DAY_NAMES = [ |
| 17 | + "Sunday", |
| 18 | + "Monday", |
| 19 | + "Tuesday", |
| 20 | + "Wednesday", |
| 21 | + "Thursday", |
| 22 | + "Friday", |
| 23 | + "Saturday", |
| 24 | +] |
| 25 | + |
| 26 | + |
| 27 | +def _validate_inputs(boxstyle, dates, values, week_starts_on): |
| 28 | + implemented_boxstyle = [ |
| 29 | + "square", |
| 30 | + "circle", |
| 31 | + "round", |
| 32 | + "round4", |
| 33 | + "sawtooth", |
| 34 | + "roundtooth", |
| 35 | + ] |
| 36 | + not_implemented_boxstyle = [ |
| 37 | + "ellipse", |
| 38 | + "larrow", |
| 39 | + "rarrow", |
| 40 | + "darrow", |
| 41 | + ] |
| 42 | + if isinstance(boxstyle, str): |
| 43 | + if boxstyle not in implemented_boxstyle: |
| 44 | + if boxstyle in not_implemented_boxstyle: |
| 45 | + return NotImplementedError |
| 46 | + else: |
| 47 | + raise ValueError( |
| 48 | + f"Invalid `boxstyle` value. Must be in {implemented_boxstyle}" |
| 49 | + ) |
| 50 | + elif not isinstance(boxstyle, matplotlib.patches.BoxStyle): |
| 51 | + raise ValueError( |
| 52 | + f"`boxstyle` must either be a string or a `matplotlib.patches.BoxStyle`, not {boxstyle}" |
| 53 | + ) |
| 54 | + |
| 55 | + if len(dates) != len(values): |
| 56 | + raise ValueError("`dates` and `values` must have the same length.") |
| 57 | + |
| 58 | + if len(dates) == 0 or len(values) == 0: |
| 59 | + raise ValueError("`dates` and `values` cannot be empty.") |
| 60 | + |
| 61 | + if week_starts_on not in DAY_NAMES: |
| 62 | + raise ValueError( |
| 63 | + f"Invalid start_day string: {week_starts_on}. Must be one of {DAY_NAMES}." |
| 64 | + ) |
| 65 | + |
| 66 | + |
16 | 67 | def calendar( |
17 | 68 | dates: List[Union[date, datetime, str]], |
18 | 69 | values: List[Union[int, float]], |
@@ -104,70 +155,22 @@ def calendar( |
104 | 155 | Notes: |
105 | 156 | The function aggregates multiple entries for the same date by summing their values. |
106 | 157 | """ |
| 158 | + _validate_inputs(boxstyle, dates, values, week_starts_on) |
107 | 159 |
|
108 | 160 | month_kws = month_kws or {} |
109 | 161 | day_kws = day_kws or {} |
110 | 162 |
|
111 | | - implemented_boxstyle = [ |
112 | | - "square", |
113 | | - "circle", |
114 | | - "round", |
115 | | - "round4", |
116 | | - "sawtooth", |
117 | | - "roundtooth", |
118 | | - ] |
119 | | - not_implemented_boxstyle = [ |
120 | | - "ellipse", |
121 | | - "larrow", |
122 | | - "rarrow", |
123 | | - "darrow", |
124 | | - ] |
125 | | - if isinstance(boxstyle, str): |
126 | | - if boxstyle not in implemented_boxstyle: |
127 | | - if boxstyle in not_implemented_boxstyle: |
128 | | - return NotImplementedError |
129 | | - else: |
130 | | - raise ValueError( |
131 | | - f"Invalid `boxstyle` value. Must be in {implemented_boxstyle}" |
132 | | - ) |
133 | | - elif not isinstance(boxstyle, matplotlib.patches.BoxStyle): |
134 | | - raise ValueError( |
135 | | - f"`boxstyle` must either be a string or a `matplotlib.patches.BoxStyle`, not {boxstyle}" |
136 | | - ) |
137 | | - |
138 | | - if len(dates) != len(values): |
139 | | - raise ValueError("`dates` and `values` must have the same length.") |
140 | | - |
141 | | - if len(dates) == 0 or len(values) == 0: |
142 | | - raise ValueError("`dates` and `values` cannot be empty.") |
143 | | - |
144 | | - day_names = [ |
145 | | - "Sunday", |
146 | | - "Monday", |
147 | | - "Tuesday", |
148 | | - "Wednesday", |
149 | | - "Thursday", |
150 | | - "Friday", |
151 | | - "Saturday", |
152 | | - ] |
153 | | - if week_starts_on not in day_names: |
154 | | - raise ValueError( |
155 | | - f"Invalid start_day string: {week_starts_on}. Must be one of {day_names}." |
156 | | - ) |
157 | | - |
158 | | - # Get the index of the starting day of the week |
159 | | - week_starts_on_idx = day_names.index(week_starts_on.capitalize()) |
| 163 | + week_starts_on_index = DAY_NAMES.index(week_starts_on.capitalize()) |
160 | 164 |
|
161 | 165 | # Create a mapping from Python's weekday() (0=Monday) to our day indices |
162 | | - # where the first day is determined by week_starts_on_idx |
| 166 | + # where the first day is determined by week_starts_on_index |
163 | 167 | weekday_mapping = {} |
164 | 168 | for i in range(7): |
165 | | - # Python's weekday: 0 = Monday, ..., 6 = Sunday |
166 | 169 | python_weekday = i # 0-6 (Mon-Sun) |
167 | 170 | # Convert to our indexing where 0 = Sunday, ..., 6 = Saturday |
168 | 171 | our_day_idx = (python_weekday + 1) % 7 # Convert to 0-6 (Sun-Sat) |
169 | 172 | # Adjust for the week start day |
170 | | - adjusted_idx = (our_day_idx - week_starts_on_idx) % 7 |
| 173 | + adjusted_idx = (our_day_idx - week_starts_on_index) % 7 |
171 | 174 | weekday_mapping[python_weekday] = adjusted_idx |
172 | 175 |
|
173 | 176 | date_counts = defaultdict(float) |
@@ -204,10 +207,9 @@ def calendar( |
204 | 207 | data_for_plot = [] |
205 | 208 | for d in full_range: |
206 | 209 | days_from_start = (d - start_date).days |
207 | | - # Use our mapping to get the correct weekday |
208 | 210 | start_date_adj_weekday = weekday_mapping[start_date.weekday()] |
209 | 211 | week_index = (days_from_start + start_date_adj_weekday) // 7 |
210 | | - day_of_week = weekday_mapping[d.weekday()] # Use the mapping |
| 212 | + day_of_week = weekday_mapping[d.weekday()] |
211 | 213 | count = date_counts.get(d, 0) |
212 | 214 | data_for_plot.append((week_index, day_of_week, count)) |
213 | 215 |
|
@@ -304,9 +306,9 @@ def calendar( |
304 | 306 |
|
305 | 307 | ticks = [0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5] |
306 | 308 | # Create labels in the adjusted order based on week_starts_on |
307 | | - labels = day_names.copy() |
| 309 | + labels = DAY_NAMES.copy() |
308 | 310 | labels = [L[:3] for L in labels] # Abbreviate to "Sun", "Mon", etc. |
309 | | - adjusted_labels = labels[week_starts_on_idx:] + labels[:week_starts_on_idx] |
| 311 | + adjusted_labels = labels[week_starts_on_index:] + labels[:week_starts_on_index] |
310 | 312 |
|
311 | 313 | for y_tick, day_label in zip(ticks, adjusted_labels): |
312 | 314 | ax.text(-day_x_margin, y_tick, day_label, **day_text_style) |
|
0 commit comments