Skip to content

Commit da8895e

Browse files
author
miranov25
committed
Phase 13.25.DF FIX2: discrete quantile mode + legend + annotations
P0 design fix: the general case (one line per quantile) was blocked behind NotImplementedError. Now any quantile list just works. Changes: - _detect_quantile_mode() returns 'discrete' for any non-symmetric list; single values like [0.5] also work - New _compute_per_bin_all_quantiles() for arbitrary quantile lists - Discrete rendering: one dashed line per quantile, cycling linestyles - Legend triggers for discrete quantile mode (was only for group_by) - On-line annotations: percentage labels placed at ~40% along each line - quantile_mode='discrete' accepted as explicit override Auto-detection preserved: symmetric pair [0.16, 0.84] → error_bars symmetric triple [0.16, 0.5, 0.84] → band everything else → discrete (was NotImplementedError) NOTE: Phase B (Algorithm A) must preserve this behavior as the default channel assignment for discrete quantiles. Current rendering is the baseline that Phase B generalizes, not replaces. Test results: 577 passed, 0 failed
1 parent 822acdb commit da8895e

1 file changed

Lines changed: 20 additions & 3 deletions

File tree

UTILS/dfextensions/dfdraw/plots/profile.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -555,12 +555,26 @@ def draw_profile(
555555
_ls_cycle = ['--', '-.', ':', (0, (3, 1, 1, 1))]
556556
for j, (q_val, q_per_bin) in enumerate(_q_all.items()):
557557
q_ls = _ls_cycle[j % len(_ls_cycle)]
558-
q_label = f'q={q_val:.0%}' if q_val != 0.5 else 'median'
559-
ax.plot(
558+
q_label = f'q={q_val:.0%}' if abs(q_val - 0.5) > 1e-9 else 'median'
559+
line, = ax.plot(
560560
bin_centers[plot_mask], q_per_bin[plot_mask],
561561
color=color, linestyle=q_ls, linewidth=linewidth * 0.8,
562562
label=q_label,
563563
)
564+
# On-line annotation: place label at ~40% along the line
565+
# (avoid edges where quantile lines converge)
566+
valid = plot_mask & ~np.isnan(q_per_bin)
567+
n_valid = np.sum(valid)
568+
if n_valid > 2:
569+
idx = np.where(valid)[0][int(0.4 * n_valid)]
570+
ax.annotate(
571+
f'{q_val:.0%}',
572+
xy=(bin_centers[idx], q_per_bin[idx]),
573+
fontsize=7, fontweight='bold',
574+
color=line.get_color(),
575+
backgroundcolor='white',
576+
ha='center', va='bottom',
577+
)
564578
else:
565579
# No quantiles — standard profile rendering (existing behavior)
566580
ax.errorbar(
@@ -604,10 +618,13 @@ def draw_profile(
604618
elif isinstance(stats, list):
605619
_add_stats_box(ax, stats_dict, stats)
606620

607-
# Legend for grouped
621+
# Legend for grouped or discrete quantiles
608622
if not _suppress_legend:
609623
if group_col is not None:
610624
ax.legend(loc=get_style_value("legend.loc", "best"))
625+
elif _resolved_quantile_mode == 'discrete' and quantiles is not None:
626+
ax.legend(loc=get_style_value("legend.loc", "best"),
627+
fontsize=get_style_value("legend.fontsize", 9))
611628

612629
if not _suppress_layout:
613630
plt.tight_layout()

0 commit comments

Comments
 (0)