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
32 changes: 27 additions & 5 deletions src/call_plots.jl
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ Makes a plot from a `PowerAnalytics.PowerData` object, such as the result of
- `powerdata::PowerAnalytics.PowerData`: The `PowerData` object to be plotted

# Accepted Key Words
- `combine_categories::Bool = false` : plot category values or each value in a category
- `combine_categories::Bool = true` : plot one series per aggregated category (`true`) or one series per component, named `Category__Component` (`false`)
- `curtailment::Bool`: plot the curtailment with the variable
- `set_display::Bool = true`: set to false to prevent the plots from displaying
- `save::String = "file_path"`: set a file path to save the plots
Expand All @@ -495,6 +495,28 @@ end
return plot_powerdata_plotly!(_empty_plot_plotly(), powerdata; kwargs...)
end

"""
Concatenate the per-category `DataFrame`s of a `PowerData` into the single wide
`DataFrame` that the plotting backends take, qualifying every column as
`Category__Component`. The qualification follows PowerAnalytics'
`metric_selector_to_string` convention and is what keeps the names unique: the
same component name recurs across categories (e.g. a battery under both
`ActivePowerInVariable__EnergyReservoirStorage` and `ActivePowerOutVariable__…`).
Categories are visited in sorted order because `Dict` iteration order is not
stable and would otherwise shuffle series and legend entries between calls.
"""
function _flatten_categories(data::Dict{Symbol, DataFrames.DataFrame})
dfs = DataFrames.DataFrame[]
for key in sort(collect(keys(data)))
df = PA.no_datetime(data[key])
isempty(df) && continue
DataFrames.rename!(c -> string(key) * PA.COMPONENT_NAME_DELIMITER * c, df)
push!(dfs, df)
end
isempty(dfs) && return DataFrames.DataFrame()
return DataFrames.hcat(dfs...)
end

function _plot_powerdata!(p, powerdata::PA.PowerData, backend; kwargs...)
title = get(kwargs, :title, "")
set_display = get(kwargs, :set_display, true)
Expand All @@ -505,7 +527,7 @@ function _plot_powerdata!(p, powerdata::PA.PowerData, backend; kwargs...)
names = get(kwargs, :names, nothing)
data = PA.combine_categories(powerdata.data; names = names, aggregate = aggregate)
else
data = powerdata.data
data = _flatten_categories(powerdata.data)
end
kwargs =
Dict{Symbol, Any}((k, v) for (k, v) in kwargs if k ∉ [:title, :save, :set_display])
Expand Down Expand Up @@ -535,7 +557,7 @@ variant renders with the PlotlyLight backend instead of CairoMakie.
- `powerdata::PowerAnalytics.PowerData`: The `PowerData` object to be plotted

# Accepted Key Words
- `combine_categories::Bool = false` : plot category values or each value in a category
- `combine_categories::Bool = true` : plot one series per aggregated category (`true`) or one series per component, named `Category__Component` (`false`)
- `curtailment::Bool`: plot the curtailment with the variable
- `set_display::Bool = true`: set to false to prevent the plots from displaying
- `save::String = "file_path"`: set a file path to save the plots
Expand Down Expand Up @@ -572,7 +594,7 @@ Makes a plot from a results dictionary object
- `results::Dict{String, DataFrame`: The results to be plotted

# Accepted Key Words
- `combine_categories::Bool = false` : plot category values or each value in a category
- `combine_categories::Bool = true` : plot one series per aggregated category (`true`) or one series per component, named `Category__Component` (`false`)
- `curtailment::Bool`: plot the curtailment with the variable
- `set_display::Bool = true`: set to false to prevent the plots from displaying
- `save::String = "file_path"`: set a file path to save the plots
Expand Down Expand Up @@ -609,7 +631,7 @@ Makes a plot from a results dictionary
- `results::Dict{String, DataFrame}`: The results to be plotted

# Accepted Key Words
- `combine_categories::Bool = false` : plot category values or each value in a category
- `combine_categories::Bool = true` : plot one series per aggregated category (`true`) or one series per component, named `Category__Component` (`false`)
- `curtailment::Bool`: plot the curtailment with the variable
- `set_display::Bool = true`: set to false to prevent the plots from displaying
- `save::String = "file_path"`: set a file path to save the plots
Expand Down
27 changes: 27 additions & 0 deletions test/test_plot_creation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,32 @@ function test_plots(file_path::String; backend_pkg::String = "cairomakie")
stack = true,
)

# `combine_categories = false` (issue #111) plots one series per component
# column, qualified as `Category__Component`.
flat_names = names(PG._flatten_categories(gen_uc.data))
expected_series = sum(size(no_datetime(df), 2) for df in values(gen_uc.data))
@test length(flat_names) == expected_series
@test "ActivePowerVariable__ThermalStandard__Solitude" in flat_names
# `test_batt` appears in both storage categories: it must stay two series
@test filter(n -> endswith(n, "__test_batt"), flat_names) == [
"ActivePowerInVariable__EnergyReservoirStorage__test_batt",
"ActivePowerOutVariable__EnergyReservoirStorage__test_batt",
]
p = plot_powerdata_fn(
gen_uc;
set_display = set_display,
title = "pg_data_uncombined",
save = out_path,
combine_categories = false,
)
plot_length = backend_pkg == "cairomakie" ? p.series_count : length(p.data)
@test plot_length == expected_series

# combining still collapses each category to a single series
p = plot_powerdata_fn(gen_uc; set_display = set_display)
plot_length = backend_pkg == "cairomakie" ? p.series_count : length(p.data)
@test plot_length == length(gen_uc.data)

list = readdir(out_path)
# PlotlyLight only supports HTML export, CairoMakie supports PNG
file_ext = backend_pkg == "plotlylight" ? ".html" : ".png"
Expand All @@ -156,6 +182,7 @@ function test_plots(file_path::String; backend_pkg::String = "cairomakie")
"pg_data_stack$file_ext",
"pg_data_bar$file_ext",
"pg_data_bar_stack$file_ext",
"pg_data_uncombined$file_ext",
]
# expected results not created
@test isempty(setdiff(expected_files, list))
Expand Down