Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 40 additions & 18 deletions ext/plot_recipes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ function PowerGraphics._dataframe_plots_internal(
linestyle = get(kwargs, :linestyle, :solid)
linewidth = get(kwargs, :linewidth, 1)

time_interval = PowerGraphics.IS.convert_compound_period(
length(time_range) * (time_range[2] - time_range[1]),
)
interval =
Dates.Millisecond(Dates.Hour(1)) / Dates.Millisecond(time_range[2] - time_range[1])
# `time_range` is a `DateTime` axis for every results-driven plot, but the
# transform plots (duration curve, histogram) pass a numeric axis instead.
# These three helpers dispatch on that so the drawing code below stays shared.
temporal = PowerGraphics._is_temporal(time_range)
x_label = PowerGraphics._x_axis_label(time_range, get(kwargs, :x_label, nothing))
interval = PowerGraphics._x_interval(time_range)

if isnothing(plot)
plot = PowerGraphics._empty_plot(backend)
Expand All @@ -64,7 +65,7 @@ function PowerGraphics._dataframe_plots_internal(

# CairoMakie.band doesn't allow for DateTime axes. Every plot now gets
# float axes instead so plots can be layered on the same Axis.
time_range_float = Dates.datetime2unix.(time_range)
x_float = PowerGraphics._x_values(time_range)

data = Matrix(ndf)
power_scale = get(kwargs, :power_scale, 1.0)
Expand All @@ -73,7 +74,7 @@ function PowerGraphics._dataframe_plots_internal(
end
labels = [label_fn(label) for label in column_names]

plot.axis.xlabel = "$time_interval"
plot.axis.xlabel = x_label
plot.axis.ylabel = get(kwargs, :y_label, "")
if title != " " # Only set title if not default
plot.axis.title = title
Expand All @@ -85,7 +86,23 @@ function PowerGraphics._dataframe_plots_internal(
# manually with PolyElement below.
bar_legend_entries = nothing

if bar
if bar && !temporal
# A numeric x axis carries its own bar positions (histogram bin centers),
# so there is nothing to integrate over time — each row is already a bar.
# Series are overlaid with transparency so distributions stay comparable.
bar_width = length(x_float) > 1 ? x_float[2] - x_float[1] : 1.0
for ix in 1:length(labels)
CairoMakie.barplot!(
plot.axis,
x_float,
data[:, ix];
color = (seriescolor[ix], 0.6),
label = string(labels[ix]),
width = bar_width,
)
end
plot.axis.xgridvisible = false
elseif bar
plot_data = sum(data; dims = 1) ./ interval

if stack
Expand Down Expand Up @@ -148,7 +165,7 @@ function PowerGraphics._dataframe_plots_internal(
if stair
CairoMakie.stairs!(
plot.axis,
time_range_float,
x_float,
outer;
color = color,
label = string(labels[ix]),
Expand All @@ -158,7 +175,7 @@ function PowerGraphics._dataframe_plots_internal(
)
CairoMakie.band!(
plot.axis,
time_range_float,
x_float,
lo,
up;
color = (color, 0.3),
Expand All @@ -171,7 +188,7 @@ function PowerGraphics._dataframe_plots_internal(
# the stack.
CairoMakie.band!(
plot.axis,
time_range_float,
x_float,
lo,
up;
color = (color, 0.7),
Expand All @@ -191,7 +208,7 @@ function PowerGraphics._dataframe_plots_internal(
if stair
CairoMakie.stairs!(
plot.axis,
time_range_float,
x_float,
outer;
color = color,
label = string(labels[ix]),
Expand All @@ -202,7 +219,7 @@ function PowerGraphics._dataframe_plots_internal(
else
CairoMakie.lines!(
plot.axis,
time_range_float,
x_float,
outer;
color = color,
label = string(labels[ix]),
Expand All @@ -217,7 +234,7 @@ function PowerGraphics._dataframe_plots_internal(
if stair
CairoMakie.stairs!(
plot.axis,
time_range_float,
x_float,
data[:, ix];
color = color,
label = string(labels[ix]),
Expand All @@ -228,7 +245,7 @@ function PowerGraphics._dataframe_plots_internal(
else
CairoMakie.lines!(
plot.axis,
time_range_float,
x_float,
data[:, ix];
color = color,
label = string(labels[ix]),
Expand All @@ -239,9 +256,14 @@ function PowerGraphics._dataframe_plots_internal(
end
end

tick_positions = [time_range_float[1], last(time_range_float)]
tick_labels = string.([time_range[1], last(time_range)])
plot.axis.xticks = (tick_positions, tick_labels)
# A DateTime axis is drawn as unix seconds, which auto-ticks into
# meaningless numbers — label the endpoints instead. A numeric axis
# already ticks sensibly on its own.
if temporal
tick_positions = [x_float[1], last(x_float)]
tick_labels = string.([time_range[1], last(time_range)])
plot.axis.xticks = (tick_positions, tick_labels)
end
end

CairoMakie.reset_limits!(plot.axis)
Expand Down
45 changes: 36 additions & 9 deletions ext/plotly_recipes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ function PowerGraphics._dataframe_plots_internal(
)[(plot_length + 1):end],
)

time_interval = PowerGraphics.IS.convert_compound_period(
length(time_range) * (time_range[2] - time_range[1]),
)
interval =
Dates.Millisecond(Dates.Hour(1)) / Dates.Millisecond(time_range[2] - time_range[1])
# `time_range` is a `DateTime` axis for every results-driven plot, but the
# transform plots (duration curve, histogram) pass a numeric axis instead.
# These helpers dispatch on that so the trace code below stays shared;
# Plotly consumes both element types as `x` directly.
temporal = PowerGraphics._is_temporal(time_range)
x_label = PowerGraphics._x_axis_label(time_range, get(kwargs, :x_label, nothing))
interval = PowerGraphics._x_interval(time_range)

if isempty(variable)
@warn "Plot dataframe empty: skipping plot creation"
Expand All @@ -60,7 +62,23 @@ function PowerGraphics._dataframe_plots_internal(
line_shape = get(kwargs, :stair, false) ? "hv" : "linear"
line_dash = get(kwargs, :line_dash, "solid")

if bar
if bar && !temporal
# A numeric x axis carries its own bar positions (histogram bin centers),
# so there is nothing to integrate over time — each row is already a bar.
for ix = 1:length(names)
plot(
PlotlyLight.Config(;
type = "bar",
x = time_range,
y = plot_data[:, ix],
marker = PlotlyLight.Config(; color = seriescolor[ix]),
name = names[ix],
opacity = 0.6,
showlegend = true,
),
)
end
elseif bar
plot_data = sum(plot_data; dims = 1) ./ interval
if nofill
plot_data = [plot_data; plot_data]
Expand Down Expand Up @@ -154,10 +172,19 @@ function PowerGraphics._dataframe_plots_internal(
plot.layout.yaxis.showticklabels = true
plot.layout.yaxis.rangemode = "tozero"
plot.layout.yaxis.title.text = y_label
plot.layout.xaxis.showticklabels = !bar
plot.layout.xaxis.title.text = string(time_interval)
# Time-axis bar plots collapse to one bar per series, so their tick labels are
# redundant with the legend; a numeric axis needs its ticks.
plot.layout.xaxis.showticklabels = !(bar && temporal)
plot.layout.xaxis.title.text = x_label
plot.layout.title.text = title
plot.layout.barmode = stack ? "relative" : "group"
if stack
plot.layout.barmode = "relative"
elseif bar && !temporal
# Overlay rather than dodge so histogram series remain aligned on shared bins.
plot.layout.barmode = "overlay"
else
plot.layout.barmode = "group"
end

legend_position = get(kwargs, :legend_position, :right)
legend_font_size = get(kwargs, :legend_font_size, nothing)
Expand Down
4 changes: 4 additions & 0 deletions src/PowerGraphics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ export plot_dataframe, plot_dataframe_plotly
export plot_powerdata, plot_powerdata_plotly
export plot_results, plot_results_plotly
export plot_fuel, plot_fuel_plotly
export plot_duration_curve, plot_duration_curve_plotly
export plot_histogram, plot_histogram_plotly
export plot_demand!, plot_demand_plotly!
export plot_dataframe!, plot_dataframe_plotly!
export plot_powerdata!, plot_powerdata_plotly!
export plot_results!, plot_results_plotly!
export plot_fuel!, plot_fuel_plotly!
export plot_duration_curve!, plot_duration_curve_plotly!
export plot_histogram!, plot_histogram_plotly!
export report
export save_plot
export label_component, label_variable, label_acronym, label_first_word
Expand Down
Loading