Skip to content

Commit 7bdec7f

Browse files
committed
Add trace-specific color sequences in Plotly Express via templates
- Modify apply_default_cascade to read colors from template.data.<trace_type> - Prioritize trace-specific colors over layout.colorway - Add special case for timeline constructor (maps to bar trace type) - Add comprehensive tests for trace-specific color sequences - Test trace type isolation, fallback behavior, and timeline special case
1 parent 83faec8 commit 7bdec7f

File tree

2 files changed

+25
-68
lines changed

2 files changed

+25
-68
lines changed

plotly/express/_core.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,19 +1047,11 @@ def apply_default_cascade(args, constructor=None):
10471047
else:
10481048
trace_type = constructor().type
10491049
if trace_data_list := getattr(args["template"].data, trace_type, None):
1050-
# try marker.color first
10511050
args["color_discrete_sequence"] = [
10521051
trace_data.marker.color
10531052
for trace_data in trace_data_list
10541053
if hasattr(trace_data, "marker")
10551054
]
1056-
# fallback to line.color if marker.color not available
1057-
if not args["color_discrete_sequence"] or not any(args["color_discrete_sequence"]):
1058-
args["color_discrete_sequence"] = [
1059-
trace_data.line.color
1060-
for trace_data in trace_data_list
1061-
if hasattr(trace_data, "line")
1062-
]
10631055
# if no trace-specific colors found, reset to None to allow fallback
10641056
if not args["color_discrete_sequence"] or not any(args["color_discrete_sequence"]):
10651057
args["color_discrete_sequence"] = None

tests/test_optional/test_px/test_px.py

Lines changed: 25 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -227,81 +227,46 @@ def test_px_templates(backend):
227227

228228

229229
def test_px_templates_trace_specific_colors(backend):
230-
import plotly.graph_objects as go
230+
import pandas as pd
231231

232232
tips = px.data.tips(return_type=backend)
233233

234-
# read trace-specific colors from template.data.histogram
235-
histogram_template = go.layout.Template()
236-
histogram_template.data.histogram = [
237-
go.Histogram(marker=dict(color="orange")),
238-
go.Histogram(marker=dict(color="purple")),
239-
]
240-
fig = px.histogram(tips, x="total_bill", color="sex", template=histogram_template)
234+
# trace-specific colors: each trace type uses its own template colors
235+
template = {
236+
"data_histogram": [
237+
{"marker": {"color": "orange"}},
238+
{"marker": {"color": "purple"}},
239+
],
240+
"data_bar": [
241+
{"marker": {"color": "red"}},
242+
{"marker": {"color": "blue"}},
243+
],
244+
"layout_colorway": ["yellow", "green"],
245+
}
246+
# histogram uses histogram colors
247+
fig = px.histogram(tips, x="total_bill", color="sex", template=template)
241248
assert fig.data[0].marker.color == "orange"
242249
assert fig.data[1].marker.color == "purple"
243-
244-
# scatter uses template.data.scatter colors, not histogram colors
245-
scatter_template = go.layout.Template()
246-
scatter_template.data.scatter = [
247-
go.Scatter(marker=dict(color="cyan")),
248-
go.Scatter(marker=dict(color="magenta")),
249-
]
250-
scatter_template.data.histogram = [
251-
go.Histogram(marker=dict(color="orange")),
252-
]
253-
fig = px.scatter(tips, x="total_bill", y="tip", color="sex", template=scatter_template)
254-
assert fig.data[0].marker.color == "cyan"
255-
assert fig.data[1].marker.color == "magenta"
256-
257-
# histogram still uses histogram colors even when scatter colors exist
258-
fig = px.histogram(tips, x="total_bill", color="sex", template=scatter_template)
259-
assert fig.data[0].marker.color == "orange"
260-
261250
# fallback to layout.colorway when trace-specific colors don't exist
262-
fig = px.histogram(
263-
tips, x="total_bill", color="sex", template=dict(layout_colorway=["yellow", "green"])
264-
)
251+
fig = px.box(tips, x="day", y="total_bill", color="sex", template=template)
265252
assert fig.data[0].marker.color == "yellow"
266253
assert fig.data[1].marker.color == "green"
267-
268254
# timeline special case (maps to bar)
269-
timeline_template = go.layout.Template()
270-
timeline_template.data.bar = [
271-
go.Bar(marker=dict(color="red")),
272-
go.Bar(marker=dict(color="blue")),
273-
]
274-
timeline_data = {
275-
"Task": ["Job A", "Job B"],
276-
"Start": ["2009-01-01", "2009-03-05"],
277-
"Finish": ["2009-02-28", "2009-04-15"],
278-
"Resource": ["Alex", "Max"],
279-
}
280-
# Use same backend as tips for consistency
281-
df_timeline = px.data.tips(return_type=backend)
282-
df_timeline = nw.from_native(df_timeline).with_columns(
283-
nw.lit("Job A").alias("Task"),
284-
nw.lit("2009-01-01").alias("Start"),
285-
nw.lit("2009-02-28").alias("Finish"),
286-
nw.lit("Alex").alias("Resource"),
287-
).head(1).to_native()
288-
# Add second row
289-
df_timeline2 = nw.from_native(df_timeline).with_columns(
290-
nw.lit("Job B").alias("Task"),
291-
nw.lit("2009-03-05").alias("Start"),
292-
nw.lit("2009-04-15").alias("Finish"),
293-
nw.lit("Max").alias("Resource"),
294-
).head(1).to_native()
295-
# Combine - actually, this is getting too complex. Let me just use a simpler approach
296-
import pandas as pd
297-
df_timeline = pd.DataFrame(timeline_data)
255+
df_timeline = pd.DataFrame(
256+
{
257+
"Task": ["Job A", "Job B"],
258+
"Start": ["2009-01-01", "2009-03-05"],
259+
"Finish": ["2009-02-28", "2009-04-15"],
260+
"Resource": ["Alex", "Max"],
261+
}
262+
)
298263
fig = px.timeline(
299264
df_timeline,
300265
x_start="Start",
301266
x_end="Finish",
302267
y="Task",
303268
color="Resource",
304-
template=timeline_template,
269+
template=template,
305270
)
306271
assert fig.data[0].marker.color == "red"
307272
assert fig.data[1].marker.color == "blue"

0 commit comments

Comments
 (0)