diff --git a/Project.toml b/Project.toml index 63ef4c1..cd0cc48 100644 --- a/Project.toml +++ b/Project.toml @@ -9,15 +9,18 @@ TimeZones = "f269a46b-ccf7-5d73-abea-4c690281aa53" [weakdeps] DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [extensions] TimeStructDataFramesExt = "DataFrames" +TimeStructMakieExt = "Makie" TimeStructUnitfulExt = "Unitful" [compat] DataFrames = "1" Dates = "1" +Makie = "0.21" TimeZones = "1" Unitful = "1" julia = "^1.9" diff --git a/ext/TimeStructMakieExt.jl b/ext/TimeStructMakieExt.jl new file mode 100644 index 0000000..80d9860 --- /dev/null +++ b/ext/TimeStructMakieExt.jl @@ -0,0 +1,39 @@ +module TimeStructMakieExt +using Makie +using TimeStruct +import TimeStruct: profilechart, profilechart! + +function Makie.convert_arguments(P::PointBased, periods, profile::TimeProfile) + pts = [Point2(start_oper_time(t, periods), profile[t]) for t in periods] + l = last(periods) + push!(pts, Point2(end_oper_time(l, periods), profile[l])) + return (pts,) +end + +@recipe(ProfileChart) do scene + return Attributes(type = :stairs) +end + +function Makie.plot!(sc::ProfileChart{<:Tuple{<:TimeStructure,<:TimeProfile}}) + periods = sc[1] + profile = sc[2] + + for opscen in opscenarios(periods[]) + stairs!(sc, opscen, profile[]; step = :post) + end + + return sc +end + +function Makie.plot(periods::TwoLevel, profile::TimeProfile) + fig = Figure() + for (i, sp) in enumerate(strat_periods(periods)) + fig[1, i] = Axis(fig, title = "sp = $sp") + for opscen in opscenarios(sp) + stairs!(fig[1, i], opscen, profile; step = :post) + end + end + return fig +end + +end diff --git a/src/TimeStruct.jl b/src/TimeStruct.jl index 53aa4d9..bf46f60 100644 --- a/src/TimeStruct.jl +++ b/src/TimeStruct.jl @@ -22,9 +22,9 @@ include("op_scenarios/rep_periods.jl") include("op_scenarios/strat_periods.jl") include("op_scenarios/tree_periods.jl") -include("utils.jl") include("discount.jl") include("profiles.jl") +include("utils.jl") export TimeStructure export SimpleTimes @@ -58,4 +58,6 @@ export Discounter export discount export objective_weight +export profilechart, profilechart! + end # module diff --git a/src/utils.jl b/src/utils.jl index 8ce1bf6..47f5232 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -123,6 +123,14 @@ function end_oper_time(t::TimePeriod, ts::OperationalScenarios) return end_oper_time(t, ts.scenarios[_opscen(t)]) end +function end_oper_time(t::TimePeriod, opscen::AbstractOperationalScenario) + return end_oper_time(t, opscen.operational) +end + +function end_oper_time(t::TimePeriod, ts::RepresentativePeriods) + return end_oper_time(t, ts.rep_periods[_rper(t)]) +end + function end_oper_time(t::TimePeriod, ts::TwoLevel) return end_oper_time(t, ts.operational[_strat_per(t)]) end @@ -155,3 +163,85 @@ function Base.iterate(ts::TreeStructure, state = nothing) return TreePeriod(ts, next[1]), next[2] end + +function profilechart end +function profilechart! end + +_scen(sc::AbstractOperationalScenario) = "sc-$(TimeStruct._opscen(sc))" +_repr(rp::AbstractRepresentativePeriod) = "rp-$(TimeStruct._rper(rp))" +_strat(sp::AbstractStrategicPeriod) = "sp-$(TimeStruct._strat_per(sp))" + +function rowtable(profile::TimeProfile, periods::SimpleTimes; include_end = true) + rowtable = [] + for t in periods + push!(rowtable, (t = start_oper_time(t, periods), value = profile[t])) + end + if include_end + last_period = last(periods) + push!( + rowtable, + (t = end_oper_time(last_period, periods), value = profile[last_period]), + ) + end + return rowtable +end + +function rowtable(profile::TimeProfile, periods::OperationalScenarios; include_end = true) + rowtable = [] + for sc in opscenarios(periods) + for t in sc + push!( + rowtable, + (opscen = _scen(sc), t = start_oper_time(t, periods), value = profile[t]), + ) + end + if include_end + last_period = last(sc) + push!( + rowtable, + ( + opscen = _scen(sc), + t = end_oper_time(last_period, periods), + value = profile[last_period], + ), + ) + end + end + return rowtable +end + +function rowtable(profile::TimeProfile, periods::TwoLevel; include_end = true) + rowtable = [] + for sp in strat_periods(periods) + for rp in repr_periods(sp) + for sc in opscenarios(rp) + for t in sc + push!( + rowtable, + ( + strat = _strat(sp), + repr = _repr(rp), + opscen = _scen(sc), + t = start_oper_time(t, periods), + value = profile[t], + ), + ) + end + if include_end + last_period = last(sc) + push!( + rowtable, + ( + strat = _strat(sp), + repr = _repr(rp), + opscen = _scen(sc), + t = end_oper_time(last_period, periods), + value = profile[last_period], + ), + ) + end + end + end + end + return rowtable +end diff --git a/test/test_makie.jl b/test/test_makie.jl new file mode 100644 index 0000000..6141e0d --- /dev/null +++ b/test/test_makie.jl @@ -0,0 +1,68 @@ +using CairoMakie +using TimeStruct +using AlgebraOfGraphics + +periods = SimpleTimes([1, 2, 4, 2, 3]) +profile = OperationalProfile([2.0, 3.4, 3.5, 1.2, 0.6]) + +stairs(periods, profile; step = :post) +scatter!(periods, profile) +current_figure() + +lines(periods, profile) + +scens = OperationalScenarios(3, periods) +scen_prof = ScenarioProfile([profile, 0.8 * profile, 1.2 * profile]) +profilechart(scens, scen_prof) + +repr = RepresentativePeriods(2, [0.7, 0.3], [scens, scens]) + +two_level = TwoLevel(3, repr) +plot(two_level, scen_prof) + +# Testing with AlgebraOfGraphics and rowtables +set_aog_theme!() +axis = (width = 225, height = 225) + +tab = TimeStruct.rowtable(profile, periods) + +plt = data(tab) * mapping(:t, :value) * visual(Stairs, step = :post) +draw(plt; axis = axis) + +sc_tab = TimeStruct.rowtable(scen_prof, scens) +plt = data(sc_tab) * mapping(:t, :value, color = :opscen) * visual(Stairs, step = :post) +draw(plt; axis = axis) + +plt = data(sc_tab) * mapping(:t, :value, col = :opscen) * visual(Stairs, step = :post) +draw(plt; axis = axis) + +two_tab = TimeStruct.rowtable(scen_prof, two_level) +plt = + data(two_tab) * + mapping(:t, :value, row = :repr, col = :strat, color = :opscen) * + visual(Stairs, step = :post) +draw(plt; axis = axis) + +plt = data(two_tab) * mapping(:t, :value, layout = :strat) * visual(Stairs, step = :post) +draw(plt; axis = axis) + +using JuMP, HiGHS + +m = Model() +@variable(m, x[periods] >= 0) +@constraint(m, [t in periods], x[t] == profile[t]) +@objective(m, Min, 0) + +set_optimizer(m, HiGHS.Optimizer) +optimize!(m) + +tab = Containers.rowtable(value, x) + +set_aog_theme!() +axis = (width = 225, height = 225) + +plt = data(tab) * mapping(:x1 => (t -> start_oper_time(t, periods)), :y) +draw(plt; axis = axis) + +plt = data(tab) * mapping(:x1, :y) +draw(plt; axis = axis)