You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
PlotlyJS direct plot() fails with ZonedDateTime x-axis, but add_trace!() with Scatter works #520
plot(string.(TimeVectorZoned), YVector, mode="lines+markers", name="ZonedDateTime direct plot")
where we directly pass for the input labels as String type and let plotly interpret them (this is what is happening lower down in your workaround).
Perhaps we should document this for similar cases.
Hey, thanks for the respons @jd-foster !
I'm would not say I'm a professional programmer, so I'm happy to help the ways I can.
So thanks to the team behind PlotlyJS.jl!
Good to know that that is a workarround!
But then one thing came to mind, and I wanted to validate a scenario.
If I start with UTC ZonedDateTime values, convert them to two different time zones (for example, Europe/Lisbon and Europe/Warsaw), and then plot both series together, I would expect the data points to overlap because they represent the same absolute instants in time.
At the same time, I can also see another valid interpretation: the chart could display each series using its own local clock representation on the x-axis. So the question seems less about timestamp correctness and more about intended plotting behavior.
In my use case, data is stored in UTC and then converted to local time for visualization, so end users can read the timeline more easily. Given that, what PlotlyJS is doing now seems reasonable, although I am not 100% sure what behavior I want yet, but thats not my decision.
So I think the key clarification is:
Should plotting align by absolute instant (overlapping lines), or preserve local clock-time labels per timezone (which may appear shifted)?
I can confirm that the plot looks the same when I plot the string values for both locations, and do now "overlap" both Lisabon and Warsaw.
But if one wanted that then how would one agree on selected timezone that is shown on the x axsis? Then one would need 2 x axsis. And that might be out of scope. But would an awesome feature.
using Dates
using TimeZones
using PlotlyJS
n = 10
YVector = collect(1:n)
# Start from a UTC base time.
base_time_utc = ZonedDateTime(DateTime(2026, 4, 13, 0, 0, 0), tz"UTC")
TimeVectorUTC = [base_time_utc + Hour(i - 1) for i in 1:n]
# Convert to Portugal/Lisbon and Europe/Warsaw.
TimeVectorLisbon = astimezone.(TimeVectorUTC, Ref(tz"Europe/Lisbon"))
TimeVectorWarsaw = astimezone.(TimeVectorUTC, Ref(tz"Europe/Warsaw"))
# Convert to strings for direct plotting workaround.
StringsLisbon = string.(TimeVectorLisbon)
StringsWarsaw = string.(TimeVectorWarsaw)
trace_lisbon = scatter(
x = TimeVectorLisbon,
y = YVector,
mode = "lines+markers",
name = "Lisbon TimeVector",
line = attr(color = "blue")
)
trace_warsaw = scatter(
x = TimeVectorWarsaw,
y = YVector,
mode = "lines+markers",
name = "Warsaw TimeVector",
line = attr(color = "red")
)
trace_lisbon_string = scatter(
x = StringsLisbon,
y = YVector,
mode = "lines+markers",
name = "Lisbon string",
line = attr(color = "cyan", dash = "dash")
)
trace_warsaw_string = scatter(
x = StringsWarsaw,
y = YVector,
mode = "lines+markers",
name = "Warsaw string",
line = attr(color = "pink", dash = "dash")
)
p = plot(
[trace_lisbon, trace_warsaw, trace_lisbon_string, trace_warsaw_string],
Layout(
title = "Timezone plot test: TimeVector + string (Lisbon vs Warsaw)",
xaxis_title = "Local time",
yaxis_title = "YVector"
)
)
display(p)
I understand that the underlying plotly.js library does not try to handle time-zone conversions internally, but allows enough flexibility to work with user needs.
One example is to use subplots to that align, adapting your example:
using Dates
using TimeZones
using PlotlyJS
n =10
YVector =collect(1:n)
# Start from a UTC base time.
base_time_utc =ZonedDateTime(DateTime(2026, 4, 13, 0, 0, 0), tz"UTC")
TimeVectorUTC = [base_time_utc +Hour(i -1) for i in1:n]
# Convert to Portugal/Lisbon and Europe/Warsaw.
TimeVectorLisbon =astimezone.(TimeVectorUTC, Ref(tz"Europe/Lisbon"))
TimeVectorWarsaw =astimezone.(TimeVectorUTC, Ref(tz"Europe/Warsaw"))
trace_lisbon =scatter(
x = TimeVectorLisbon,
y = YVector,
mode ="lines+markers",
name ="Lisbon TimeVector",
line =attr(color ="blue")
)
trace_warsaw =scatter(
x = TimeVectorWarsaw,
y = YVector,
mode ="lines+markers",
name ="Warsaw TimeVector",
line =attr(color ="red")
)
lisbon_offset =first(TimeVectorLisbon).zone.offset
warsaw_offset =first(TimeVectorWarsaw).zone.offset
q =make_subplots(rows=2, cols=1, vertical_spacing=0.37);
add_trace!(q, trace_lisbon, row=1, col=1);
add_trace!(q, trace_warsaw, row=2, col=1);
relayout!(q.plot.layout,
Layout(
title ="Timezone plot test: TimeVector w/ UTC (Lisbon vs Warsaw)",
yaxis_title ="YVector",
yaxis2_title ="YVector",
xaxis =attr(tickformat="%H:%M\nUTC$(lisbon_offset) %b %e, %Y",
title ="Lisbon time",
side="bottom"),
xaxis2 =attr(tickformat="%H:%M\nUTC$(warsaw_offset) %b %e, %Y",
title="Warsaw time",
side="top")
)
)
display(q)
changed the title [-]PlotlyJS direct plot fails with ZonedDateTime x-axis, but add_trace! with Scatter works[/-][+]PlotlyJS direct plot() fails with ZonedDateTime x-axis, but add_trace!() with Scatter works[/+]on Apr 13, 2026
I don’t think so. If you’re referring to the TimeZoneOffset workaround, I don't think it’s necessary to have smarter implementation. Since its very specific.
As long as plot(TimeVectorZoned, YVector, mode="lines+markers", name="ZonedDateTime direct plot")
is fixed, I’m happy.
Activity
jd-foster commentedon Apr 13, 2026
Thanks for drawing attention to this case.
A possibly simpler workaround is
where we directly pass for the input labels as
Stringtype and letplotlyinterpret them (this is what is happening lower down in your workaround).Perhaps we should document this for similar cases.
MChristianBF commentedon Apr 13, 2026
Hey, thanks for the respons @jd-foster !
I'm would not say I'm a professional programmer, so I'm happy to help the ways I can.
So thanks to the team behind PlotlyJS.jl!
Good to know that that is a workarround!
But then one thing came to mind, and I wanted to validate a scenario.
If I start with
UTCZonedDateTime values, convert them to two different time zones (for example,Europe/LisbonandEurope/Warsaw), and then plot both series together, I would expect the data points to overlap because they represent the same absolute instants in time.At the same time, I can also see another valid interpretation: the chart could display each series using its own local clock representation on the x-axis. So the question seems less about timestamp correctness and more about intended plotting behavior.
In my use case, data is stored in UTC and then converted to local time for visualization, so end users can read the timeline more easily. Given that, what PlotlyJS is doing now seems reasonable, although I am not 100% sure what behavior I want yet, but thats not my decision.
So I think the key clarification is:
Should plotting align by absolute instant (overlapping lines), or preserve local clock-time labels per timezone (which may appear shifted)?
I can confirm that the plot looks the same when I plot the string values for both locations, and do now "overlap" both Lisabon and Warsaw.
But if one wanted that then how would one agree on selected timezone that is shown on the x axsis? Then one would need 2 x axsis. And that might be out of scope. But would an awesome feature.
jd-foster commentedon Apr 13, 2026
I understand that the underlying
plotly.jslibrary does not try to handle time-zone conversions internally, but allows enough flexibility to work with user needs.One example is to use subplots to that align, adapting your example:
[-]PlotlyJS direct plot fails with ZonedDateTime x-axis, but add_trace! with Scatter works[/-][+]PlotlyJS direct plot() fails with ZonedDateTime x-axis, but add_trace!() with Scatter works[/+]jd-foster commentedon Apr 26, 2026
Anything we could change within this package to help here?
MChristianBF commentedon Apr 26, 2026
I don’t think so. If you’re referring to the
TimeZoneOffsetworkaround, I don't think it’s necessary to have smarter implementation. Since its very specific.As long as
plot(TimeVectorZoned, YVector, mode="lines+markers", name="ZonedDateTime direct plot")is fixed, I’m happy.