matplotlib
Python
data visualization
twinx
legend

Secondary axis with twinx how to add to legend

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

matplotlib.axes.Axes.twinx() is useful when two series share the same x-axis but use very different y-axis scales. The part that usually surprises people is the legend: each Axes object manages its own artists, so calling legend() on only one axis does not automatically include lines drawn on the other axis.

Why the Legend Looks Incomplete

When you call ax.twinx(), Matplotlib creates a second Axes instance that is layered on top of the first one. Even though both axes share the x-axis, they are still separate plotting containers. That separation is why this common pattern produces an incomplete legend:

python
1import matplotlib.pyplot as plt
2
3days = [1, 2, 3, 4, 5]
4revenue = [120, 150, 170, 160, 210]
5conversion = [2.1, 2.5, 2.2, 2.8, 3.0]
6
7fig, ax = plt.subplots()
8ax2 = ax.twinx()
9
10ax.plot(days, revenue, label="Revenue")
11ax2.plot(days, conversion, label="Conversion")
12
13ax.legend()
14plt.show()

The legend will usually show only "Revenue" because ax.legend() collects labeled artists from ax, not from ax2.

Merge Handles from Both Axes

The most reliable fix is to gather legend handles and labels from both axes, then pass the combined lists into a single legend call.

python
1import matplotlib.pyplot as plt
2
3days = [1, 2, 3, 4, 5]
4revenue = [120, 150, 170, 160, 210]
5conversion = [2.1, 2.5, 2.2, 2.8, 3.0]
6
7fig, ax = plt.subplots()
8ax2 = ax.twinx()
9
10line1, = ax.plot(days, revenue, color="tab:blue", label="Revenue")
11line2, = ax2.plot(days, conversion, color="tab:red", label="Conversion %")
12
13handles = [line1, line2]
14labels = [line.get_label() for line in handles]
15ax.legend(handles, labels, loc="upper left")
16
17ax.set_xlabel("Day")
18ax.set_ylabel("Revenue", color="tab:blue")
19ax2.set_ylabel("Conversion %", color="tab:red")
20
21plt.tight_layout()
22plt.show()

This version is explicit and easy to reason about. You decide exactly which artists appear in the legend and in what order.

Use get_legend_handles_labels() for Larger Plots

If each axis contains multiple lines, bars, or other labeled artists, manually tracking every returned handle becomes tedious. In that case, let each axis report its labeled artists and then combine them.

python
1import matplotlib.pyplot as plt
2
3x = [1, 2, 3, 4]
4
5fig, ax = plt.subplots()
6ax2 = ax.twinx()
7
8ax.plot(x, [3, 4, 5, 6], label="Orders", color="tab:blue")
9ax.plot(x, [2, 3, 4, 4], label="Returns", color="tab:green")
10ax2.plot(x, [40, 55, 52, 60], label="Margin %", color="tab:orange")
11
12handles1, labels1 = ax.get_legend_handles_labels()
13handles2, labels2 = ax2.get_legend_handles_labels()
14
15ax.legend(handles1 + handles2, labels1 + labels2, loc="best")
16plt.show()

This pattern scales well and is often the cleanest answer in reusable plotting functions.

Keep the Plot Readable

A secondary axis is easy to misuse. The legend may be fixed, but the chart can still be confusing if viewers cannot tell which series belongs to which axis.

A few practical rules help:

  • use distinct colors for left-axis and right-axis series
  • set each y-axis label color to match its data
  • keep units explicit in axis labels and legend labels
  • use tight_layout() or constrained_layout=True so labels do not overlap

You can also place the legend at the figure level if axis space is tight:

python
1fig, ax = plt.subplots(constrained_layout=True)
2ax2 = ax.twinx()
3
4line1, = ax.plot([1, 2, 3], [10, 20, 15], label="Traffic")
5line2, = ax2.plot([1, 2, 3], [1.2, 1.5, 1.1], label="CTR")
6
7fig.legend([line1, line2], ["Traffic", "CTR"], loc="upper center", ncol=2)
8plt.show()

That approach is useful when you have a dense plot or want the legend centered above the entire figure instead of attached to one axis.

When a Secondary Axis Is the Wrong Tool

Sometimes the legend problem is a sign that the plot itself is doing too much. If the two series are not tightly related or the dual-axis scaling could mislead readers, separate subplots are often a better design.

Use twinx() when:

  • the series share the same x-axis naturally
  • the viewer benefits from seeing them aligned in time or sequence
  • the different units can still be understood clearly

Use separate subplots when the extra axis adds more confusion than value.

Common Pitfalls

The main mistake is calling legend() on only one axis and expecting Matplotlib to collect artists from both ax and ax2. Another is combining handles correctly but forgetting to label the plotted artists, which leaves the legend empty or generic. Developers also create dual-axis plots without matching colors and labels to their respective axes, which makes the result harder to read even when the legend is technically correct. A final problem is using twinx() for unrelated metrics when separate subplots would communicate the data more honestly.

Summary

  • 'twinx() creates a second Axes, so legends are not merged automatically.'
  • 'ax.legend() only sees artists attached to ax, not ax2.'
  • Combine handles manually or use get_legend_handles_labels() on both axes.
  • Match legend labels, line colors, and axis labels so the dual-axis plot stays readable.
  • If the chart becomes confusing, use separate subplots instead of forcing a secondary axis.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.