Skip to content

Commit f5ff03d

Browse files
authored
Make best legend placement content-aware (#496)
* Make best legend placement content-aware * Address best legend review feedback * Stabilize live best legend placement * Make legend readback regression transition-local * Ignore hidden fixed legends during auto placement * Rescore when fixed legend visibility changes * Track fixed legend obstacle movement
1 parent f23f662 commit f5ff03d

23 files changed

Lines changed: 4804 additions & 136 deletions

‎CHANGELOG.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ in the README).
88

99
## [Unreleased]
1010

11+
### Changed
12+
13+
- Unanchored Cartesian legends now use content-aware placement when `loc` is
14+
set to `"best"`. The initial static decision measures the legend
15+
against bounded line, area, scatter, bar, and annotation geometry, while the
16+
browser remeasures rendered pixels after responsive resize and settled view
17+
changes. Initial/static placement takes the exact minimum with canonical tie
18+
order; after the browser establishes a live winner, a five-percentage-point
19+
hysteresis keeps near-uniform views from making the legend hop, while an
20+
empty candidate still always beats an occupied one. Explicit locations,
21+
anchors, and polar legends remain fixed.
22+
1123
## [0.0.6] - 2026-08-07
1224

1325
### Added

‎docs/components/legends.md‎

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,45 @@ def positioned_legend_demo():
8181
return reflex_xy.chart(positioned_legend_chart, height="320px")
8282
~~~
8383

84+
## Automatically Avoid Plotted Data
85+
86+
Use `loc="best"` for an unanchored Cartesian legend that should choose the
87+
least-overlapping standard in-plot position. The browser scores the marks and
88+
important annotations it actually rendered, then rechecks the choice after a
89+
responsive resize or a settled pan/zoom. A concrete location such as
90+
`"upper left"`, an explicit `anchor=`, and polar legend placement remain fixed.
91+
92+
The initial static choice and the browser's first settled choice take the exact
93+
least-occupied candidate, using the canonical location order to break a tie.
94+
After that, the browser keeps its settled winner across view, resize, and LOD
95+
rechecks unless another candidate improves the normalized occupied fraction by
96+
at least 5 percentage points. An empty challenger always replaces an occupied
97+
winner, even when that improvement is smaller. This keeps near-uniform plots
98+
stable without hiding a clearly open corner.
99+
100+
~~~python demo exec
101+
import reflex_xy
102+
import xy
103+
104+
automatic_legend_chart = xy.line_chart(
105+
xy.line(
106+
[0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
107+
[2, 120, 80, 118, 100, 115, 110, 116, 114, 120, 112, 119],
108+
name="Actual",
109+
color="#2563eb",
110+
width=2.5,
111+
),
112+
xy.x_axis(domain=(0, 11), label="period"),
113+
xy.y_axis(domain=(0, 120), label="value"),
114+
xy.legend(loc="best"),
115+
title="Automatic least-overlap legend",
116+
)
117+
118+
119+
def automatic_legend_demo():
120+
return reflex_xy.chart(automatic_legend_chart, height="320px")
121+
~~~
122+
84123
## Multi-Column Layout and Unnamed Series
85124

86125
With many series, `ncols=2` lays the entries out in two columns; the dashed
@@ -163,8 +202,10 @@ you want to configure placement, columns, title, visibility, or styling.
163202

164203
### How do I change where the legend appears?
165204

166-
Pass `loc=` to `xy.legend()`, e.g. `xy.legend(loc="upper right")`. If more than
167-
one `legend()` component is present, the last one supplies the effective
205+
Pass `loc=` to `xy.legend()`, e.g. `xy.legend(loc="upper right")`, or use
206+
`xy.legend(loc="best")` for content-aware automatic placement. The compatible
207+
`loc=None` default and every concrete location stay fixed. If more than one
208+
`legend()` component is present, the last one supplies the effective
168209
configuration.
169210

170211
### How do I arrange legend entries in multiple columns?

0 commit comments

Comments
 (0)