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
11 changes: 5 additions & 6 deletions ext/plot_recipes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ mutable struct CairoMakiePlot
end

function PowerGraphics._empty_plot(backend::PowerGraphics.CairoMakieBackend)
# 16:9 by default — the Makie 800x600 (4:3) default deforms time-series
# stack plots too much.
fig = CairoMakie.Figure(; size = (1280, 720))
fig = CairoMakie.Figure(; size = PowerGraphics.DEFAULT_FIGURE_SIZE)
ax = CairoMakie.Axis(fig[1, 1])
return CairoMakiePlot(fig, ax, 0, false)
end
Expand All @@ -32,16 +30,17 @@ function PowerGraphics._dataframe_plots_internal(
label_fn = get(kwargs, :label_fn, PowerGraphics.label_short)
linestyle = get(kwargs, :linestyle, :solid)
linewidth = get(kwargs, :linewidth, 1)
# Not named `size`: that would shadow `Base.size`.
fig_size = get(kwargs, :size, nothing)

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])

if isnothing(plot)
plot = PowerGraphics._empty_plot(backend)
end
isnothing(plot) && (plot = PowerGraphics._empty_plot(backend))
isnothing(fig_size) || CairoMakie.resize!(plot.figure, fig_size...)

ndf = PowerGraphics.PA.no_datetime(variable)
column_names = DataFrames.names(ndf)
Expand Down
9 changes: 9 additions & 0 deletions ext/plotly_recipes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,15 @@ function PowerGraphics._dataframe_plots_internal(
plot.layout.title.text = title
plot.layout.barmode = stack ? "relative" : "group"

# Sized only on request, unlike CairoMakie: Plotly output is responsive to
# its container, and defaulting to a fixed size would change how every
# existing HTML plot renders.
fig_size = get(kwargs, :size, nothing)
if !isnothing(fig_size)
plot.layout.width = fig_size[1]
plot.layout.height = fig_size[2]
end

legend_position = get(kwargs, :legend_position, :right)
legend_font_size = get(kwargs, :legend_font_size, nothing)

Expand Down
10 changes: 10 additions & 0 deletions src/call_plots.jl
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ plot = plot_demand(res)
- `label_fn::Function = label_short`: function applied to legend labels (typically the raw `Variable__Component` strings produced by PowerAnalytics). Built-in options: `label_short`, `label_component`, `label_variable`, `label_acronym`, `label_first_word`, `label_truncate(n)`. Note that when `combine_categories = true` (the default for `plot_powerdata`, `plot_results`, and `plot_fuel`), columns are aggregated to category names *before* `label_fn` runs — those names don't contain `__`, so the default `label_short` is a no-op. Pass `combine_categories = false` to see the effect of `label_fn` on the raw labels.
- `legend_position::Symbol = :right`: legend placement, `:right` or `:bottom`
- `legend_font_size::Number`: override the legend label font size
- `size::Tuple{Int, Int} = (1280, 720)`: figure size in pixels; PlotlyLight plots stay responsive to their container unless `size` is passed
- `filter_func::Function = `[`PowerSystems.get_available`](@extref PowerSystems InfrastructureSystems.get_available-Tuple{RenewableDispatch}): filter components included in plot
""" # ^ temporary workaround for https://github.com/Sienna-Platform/PowerSystems.jl/issues/1598
function plot_demand(result::Union{IS.Results, PSY.System}; kwargs...)
Expand Down Expand Up @@ -286,6 +287,7 @@ instead of CairoMakie.
- `label_fn::Function = label_short`: function applied to legend labels (typically the raw `Variable__Component` strings produced by PowerAnalytics). Built-in options: `label_short`, `label_component`, `label_variable`, `label_acronym`, `label_first_word`, `label_truncate(n)`. Note that when `combine_categories = true` (the default for `plot_powerdata`, `plot_results`, and `plot_fuel`), columns are aggregated to category names *before* `label_fn` runs — those names don't contain `__`, so the default `label_short` is a no-op. Pass `combine_categories = false` to see the effect of `label_fn` on the raw labels.
- `legend_position::Symbol = :right`: legend placement, `:right` or `:bottom`
- `legend_font_size::Number`: override the legend label font size
- `size::Tuple{Int, Int} = (1280, 720)`: figure size in pixels; PlotlyLight plots stay responsive to their container unless `size` is passed
- `filter_func::Function = `[`PowerSystems.get_available`](@extref PowerSystems InfrastructureSystems.get_available-Tuple{RenewableDispatch}): filter components included in plot
- `palette` : color palette from [`load_palette`](@ref)
"""
Expand Down Expand Up @@ -339,6 +341,7 @@ plot = plot_dataframe(df, time_range)
- `label_fn::Function = label_short`: function applied to legend labels (typically the raw `Variable__Component` strings produced by PowerAnalytics). Built-in options: `label_short`, `label_component`, `label_variable`, `label_acronym`, `label_first_word`, `label_truncate(n)`. Note that when `combine_categories = true` (the default for `plot_powerdata`, `plot_results`, and `plot_fuel`), columns are aggregated to category names *before* `label_fn` runs — those names don't contain `__`, so the default `label_short` is a no-op. Pass `combine_categories = false` to see the effect of `label_fn` on the raw labels.
- `legend_position::Symbol = :right`: legend placement, `:right` or `:bottom`
- `legend_font_size::Number`: override the legend label font size
- `size::Tuple{Int, Int} = (1280, 720)`: figure size in pixels; PlotlyLight plots stay responsive to their container unless `size` is passed
"""
function plot_dataframe(df::DataFrames.DataFrame; kwargs...)
return plot_dataframe!(_empty_plot(), PA.no_datetime(df), df.DateTime; kwargs...)
Expand Down Expand Up @@ -413,6 +416,7 @@ If only the `DataFrame` is provided, it must have a column of `DateTime` values.
- `label_fn::Function = label_short`: function applied to legend labels (typically the raw `Variable__Component` strings produced by PowerAnalytics). Built-in options: `label_short`, `label_component`, `label_variable`, `label_acronym`, `label_first_word`, `label_truncate(n)`. Note that when `combine_categories = true` (the default for `plot_powerdata`, `plot_results`, and `plot_fuel`), columns are aggregated to category names *before* `label_fn` runs — those names don't contain `__`, so the default `label_short` is a no-op. Pass `combine_categories = false` to see the effect of `label_fn` on the raw labels.
- `legend_position::Symbol = :right`: legend placement, `:right` or `:bottom`
- `legend_font_size::Number`: override the legend label font size
- `size::Tuple{Int, Int} = (1280, 720)`: figure size in pixels; PlotlyLight plots stay responsive to their container unless `size` is passed
"""
function plot_dataframe!(p, df::DataFrames.DataFrame; kwargs...)
return _plot_dataframe!(
Expand Down Expand Up @@ -483,6 +487,7 @@ Makes a plot from a `PowerAnalytics.PowerData` object, such as the result of
- `label_fn::Function = label_short`: function applied to legend labels (typically the raw `Variable__Component` strings produced by PowerAnalytics). Built-in options: `label_short`, `label_component`, `label_variable`, `label_acronym`, `label_first_word`, `label_truncate(n)`. Note that when `combine_categories = true` (the default for `plot_powerdata`, `plot_results`, and `plot_fuel`), columns are aggregated to category names *before* `label_fn` runs — those names don't contain `__`, so the default `label_short` is a no-op. Pass `combine_categories = false` to see the effect of `label_fn` on the raw labels.
- `legend_position::Symbol = :right`: legend placement, `:right` or `:bottom`
- `legend_font_size::Number`: override the legend label font size
- `size::Tuple{Int, Int} = (1280, 720)`: figure size in pixels; PlotlyLight plots stay responsive to their container unless `size` is passed
"""
function plot_powerdata(powerdata::PA.PowerData; kwargs...)
return plot_powerdata!(_empty_plot(), powerdata; kwargs...)
Expand Down Expand Up @@ -549,6 +554,7 @@ variant renders with the PlotlyLight backend instead of CairoMakie.
- `label_fn::Function = label_short`: function applied to legend labels (typically the raw `Variable__Component` strings produced by PowerAnalytics). Built-in options: `label_short`, `label_component`, `label_variable`, `label_acronym`, `label_first_word`, `label_truncate(n)`. Note that when `combine_categories = true` (the default for `plot_powerdata`, `plot_results`, and `plot_fuel`), columns are aggregated to category names *before* `label_fn` runs — those names don't contain `__`, so the default `label_short` is a no-op. Pass `combine_categories = false` to see the effect of `label_fn` on the raw labels.
- `legend_position::Symbol = :right`: legend placement, `:right` or `:bottom`
- `legend_font_size::Number`: override the legend label font size
- `size::Tuple{Int, Int} = (1280, 720)`: figure size in pixels; PlotlyLight plots stay responsive to their container unless `size` is passed
"""
function plot_powerdata!(p, powerdata::PA.PowerData; kwargs...)
return _plot_powerdata!(p, powerdata, CairoMakieBackend(); kwargs...)
Expand Down Expand Up @@ -586,6 +592,7 @@ Makes a plot from a results dictionary object
- `label_fn::Function = label_short`: function applied to legend labels (typically the raw `Variable__Component` strings produced by PowerAnalytics). Built-in options: `label_short`, `label_component`, `label_variable`, `label_acronym`, `label_first_word`, `label_truncate(n)`. Note that when `combine_categories = true` (the default for `plot_powerdata`, `plot_results`, and `plot_fuel`), columns are aggregated to category names *before* `label_fn` runs — those names don't contain `__`, so the default `label_short` is a no-op. Pass `combine_categories = false` to see the effect of `label_fn` on the raw labels.
- `legend_position::Symbol = :right`: legend placement, `:right` or `:bottom`
- `legend_font_size::Number`: override the legend label font size
- `size::Tuple{Int, Int} = (1280, 720)`: figure size in pixels; PlotlyLight plots stay responsive to their container unless `size` is passed
"""
function plot_results(results::Dict{String, DataFrames.DataFrame}; kwargs...)
return plot_powerdata!(_empty_plot(), PA.PowerData(results); kwargs...)
Expand Down Expand Up @@ -623,6 +630,7 @@ Makes a plot from a results dictionary
- `label_fn::Function = label_short`: function applied to legend labels (typically the raw `Variable__Component` strings produced by PowerAnalytics). Built-in options: `label_short`, `label_component`, `label_variable`, `label_acronym`, `label_first_word`, `label_truncate(n)`. Note that when `combine_categories = true` (the default for `plot_powerdata`, `plot_results`, and `plot_fuel`), columns are aggregated to category names *before* `label_fn` runs — those names don't contain `__`, so the default `label_short` is a no-op. Pass `combine_categories = false` to see the effect of `label_fn` on the raw labels.
- `legend_position::Symbol = :right`: legend placement, `:right` or `:bottom`
- `legend_font_size::Number`: override the legend label font size
- `size::Tuple{Int, Int} = (1280, 720)`: figure size in pixels; PlotlyLight plots stay responsive to their container unless `size` is passed
"""
function plot_results!(p, results::Dict{String, DataFrames.DataFrame}; kwargs...)
return plot_powerdata!(p, PA.PowerData(results); kwargs...)
Expand Down Expand Up @@ -674,6 +682,7 @@ plot = plot_fuel(res)
- `label_fn::Function = label_short`: function applied to legend labels (typically the raw `Variable__Component` strings produced by PowerAnalytics). Built-in options: `label_short`, `label_component`, `label_variable`, `label_acronym`, `label_first_word`, `label_truncate(n)`. Note that when `combine_categories = true` (the default for `plot_powerdata`, `plot_results`, and `plot_fuel`), columns are aggregated to category names *before* `label_fn` runs — those names don't contain `__`, so the default `label_short` is a no-op. Pass `combine_categories = false` to see the effect of `label_fn` on the raw labels.
- `legend_position::Symbol = :right`: legend placement, `:right` or `:bottom`
- `legend_font_size::Number`: override the legend label font size
- `size::Tuple{Int, Int} = (1280, 720)`: figure size in pixels; PlotlyLight plots stay responsive to their container unless `size` is passed
- `filter_func::Function = `[`PowerSystems.get_available`](@extref PowerSystems InfrastructureSystems.get_available-Tuple{RenewableDispatch}): filter components included in plot
"""
function plot_fuel(result::IS.Results; kwargs...)
Expand Down Expand Up @@ -825,6 +834,7 @@ PlotlyLight backend instead of CairoMakie.
- `label_fn::Function = label_short`: function applied to legend labels (typically the raw `Variable__Component` strings produced by PowerAnalytics). Built-in options: `label_short`, `label_component`, `label_variable`, `label_acronym`, `label_first_word`, `label_truncate(n)`. Note that when `combine_categories = true` (the default for `plot_powerdata`, `plot_results`, and `plot_fuel`), columns are aggregated to category names *before* `label_fn` runs — those names don't contain `__`, so the default `label_short` is a no-op. Pass `combine_categories = false` to see the effect of `label_fn` on the raw labels.
- `legend_position::Symbol = :right`: legend placement, `:right` or `:bottom`
- `legend_font_size::Number`: override the legend label font size
- `size::Tuple{Int, Int} = (1280, 720)`: figure size in pixels; PlotlyLight plots stay responsive to their container unless `size` is passed
- `filter_func::Function = `[`PowerSystems.get_available`](@extref PowerSystems InfrastructureSystems.get_available-Tuple{RenewableDispatch}): filter components included in plot
- `palette` : Color palette as from [`load_palette`](@ref).
"""
Expand Down
3 changes: 3 additions & 0 deletions src/definitions.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@

# 16:9 by default — Makie's (600, 450) 4:3 default deforms time-series stack plots.
const DEFAULT_FIGURE_SIZE = (1280, 720)

# Color Definitions
const DEFAULT_PALETTE_FILE = joinpath(
dirname(dirname(pathof(PowerGraphics))),
Expand Down
46 changes: 46 additions & 0 deletions test/test_plot_creation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,39 @@ function test_plots(file_path::String; backend_pkg::String = "cairomakie")
cleanup && rm(out_path; recursive = true)
end

@testset "test $backend_pkg figure size" begin
df = gen_uc.data[:ActivePowerVariable__ThermalStandard]

p = plot_dataframe_fn(df, gen_uc.time; set_display = set_display)
if backend_pkg == "cairomakie"
@test size(p.figure.scene) == PG.DEFAULT_FIGURE_SIZE
else
# `haskey`, not property access: EasyConfig `getproperty` inserts
# the key it is asked for, which would then be serialized.
@test !haskey(p.layout, :width)
@test !haskey(p.layout, :height)
end

p = plot_dataframe_fn(
df,
gen_uc.time;
set_display = set_display,
size = (800, 400),
)
if backend_pkg == "cairomakie"
@test size(p.figure.scene) == (800, 400)
else
@test (p.layout.width, p.layout.height) == (800, 400)
end

plot_dataframe_fn!(p, df, gen_uc.time; set_display = set_display, size = (640, 480))
if backend_pkg == "cairomakie"
@test size(p.figure.scene) == (640, 480)
else
@test (p.layout.width, p.layout.height) == (640, 480)
end
end

@testset "test $backend_pkg powerdata plot production" begin
out_path = joinpath(file_path, backend_pkg * "_powerdata_plots")
!isdir(out_path) && mkdir(out_path)
Expand Down Expand Up @@ -267,6 +300,19 @@ function test_plots(file_path::String; backend_pkg::String = "cairomakie")
plot_length = backend_pkg == "cairomakie" ? p.series_count : length(p.data)
@test plot_length == 3

# `size` has to survive the kwarg splat into the PowerAnalytics getters.
p = plot_demand_fn(
sys_with_ts;
set_display = set_display,
aggregate = "System",
size = (900, 500),
)
if backend_pkg == "cairomakie"
@test size(p.figure.scene) == (900, 500)
else
@test (p.layout.width, p.layout.height) == (900, 500)
end

list = readdir(out_path)
# PlotlyLight only supports HTML export, CairoMakie supports PNG
file_ext = backend_pkg == "plotlylight" ? ".html" : ".png"
Expand Down