diff --git a/.gitignore b/.gitignore index ede1e02..1686045 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ # It records a fixed state of all packages used by the project. As such, it should not be # committed for packages, but should be committed for applications that require a static # environment. -/Manifest.toml +*Manifest.toml # Internal files for testing the system locally *.geojson @@ -20,12 +20,11 @@ /dev # Build artifacts for creating documentation generated by the Documenter package -/docs/Manifest.toml -/examples/Manifest.toml /examples/exported_files -/test/Manifest.toml /docs/build/ /docs/src/manual/NEWS.md +/docs/src/figures/example.png +/docs/src/figures/colors_visualization.png # Exported files when running the tests /examples/exported_files \ No newline at end of file diff --git a/NEWS.md b/NEWS.md index cfaa964..b687b1d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # Release notes +## Version 0.5.5 (2024-09-13) + +### Bugfix + +* Reorder buttons and menus to enable GUI on low resolution (i.e. [1280, 729]). Divided the text area into two areas (the new one for permanently showing result summary). + ## Version 0.5.4 (2024-09-09) ### Bugfix diff --git a/Project.toml b/Project.toml index 178b895..ba55c09 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "EnergyModelsGUI" uuid = "737a7361-d3b7-40e9-b1ac-59bee4c5ea2d" authors = ["Jon Vegard Venås ", "Magnus Askeland ", "Shweta Tiwari "] -version = "0.5.4" +version = "0.5.5" [deps] CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" diff --git a/docs/src/figures/EMI_geography.png b/docs/src/figures/EMI_geography.png index ac4863c..134f565 100644 Binary files a/docs/src/figures/EMI_geography.png and b/docs/src/figures/EMI_geography.png differ diff --git a/docs/src/figures/EMI_geography_Oslo.png b/docs/src/figures/EMI_geography_Oslo.png index 7343214..a13c870 100644 Binary files a/docs/src/figures/EMI_geography_Oslo.png and b/docs/src/figures/EMI_geography_Oslo.png differ diff --git a/docs/src/figures/colors_visualization.png b/docs/src/figures/colors_visualization.png deleted file mode 100644 index 0a69125..0000000 Binary files a/docs/src/figures/colors_visualization.png and /dev/null differ diff --git a/docs/src/manual/philosophy.md b/docs/src/manual/philosophy.md index e964fec..abe7a8b 100644 --- a/docs/src/manual/philosophy.md +++ b/docs/src/manual/philosophy.md @@ -15,12 +15,4 @@ Its aim is not to provide the user with an input processing routine or a method `EnergyModelsGUI` should by default be able to work with potential extension packages as it is only dependent on the case dictionary description and the variable names. However, you can provide an extension to `EnergyModelsGUI` in your `EMX` package with, *e.g.*, specific icons for the developed nodes. -In addition, if your package introduces new variables, you can provide a description of the variables in your package. - -!!! warning - Providing new names to the variables in its current form is a bit complicated. - You have to provide a file `descriptive_names.yml` for including descriptive names for both parameters of composite types and variables. - This file should include all existing names as it is only read one. - - We aim in a future version to utilize a different approach in which the both the fields of types and introduced variables are provided as entries to a dictionary. - In this situation, it is no longer necessary to copy the existing file. +In addition, if your package introduces new variables, you can provide a description of the variables in your package. \ No newline at end of file diff --git a/docs/src/manual/simple-example.md b/docs/src/manual/simple-example.md index 67c6231..ff642c9 100644 --- a/docs/src/manual/simple-example.md +++ b/docs/src/manual/simple-example.md @@ -1,4 +1,4 @@ -# Examples +# Example with functionality overview For the content of the individual examples, see the [examples](https://github.com/EnergyModelsX/EnergyModelsGUI.jl/tree/main/examples) directory in the project repository. @@ -36,31 +36,46 @@ include(joinpath(exdir, "EMI_geography.jl")) You should then get the following GUI: ![Example image for GUI](../figures/example.png) +## Functionality overview !!! note "Left visualization area" The left fraction of the window shows a visualization of the topology with the following functionality: - 1. You can move a `Node`/`Area` by holding down the left mouse button and dragging to the desired location (at which you then release the left mouse button). The `Links`/`Transmissions` to this `Node`/`Area` will be updated as well. + 1. You can move a `Node`/`Area` by holding down the left mouse button and dragging to the desired location (at which you then release the left mouse button). The `Links`/`Transmissions` to this `Node`/`Area` will be updated as well. All nodes associated with an area moves correspondingly when moving an `Area` object. The `Area` objects are indicated by double line edges as in the example below (each city is an `Area`). A selected `Node`/`Area` can also be moved using the arrow keys. 2. Selecting a `Node`/`Area`/`Link`/`Transmission` (by left-clicking) will print information about this object in the box on the top right. The selected object will have a green line style. 3. You can select multiple nodes/areas by holding down `ctrl` and left-clicking. 4. You can change the focus area (pan) of the window by holding down the right mouse button and dragging. 5. You can zoom in and out by using the scroll wheel on the mouse. - 6. Hovering over a component will show its type. + 6. Hovering over a component will show the name and its type (and investments if it has occured). + 7. A Transmission mode from the `EnergyModelsGeography` package will be dashed if it contains investment data (as can be seen in the example above from, i.e., Oslo to Bergen). A `Node` will have dashed edges if it contains investment data and the same for assosiated `Link`s. + 8. The legend box of `Resource`s shows an overview of all resources in `case[:Products]`. + 9. The upper-left label indicates at which level of the topology you are at (`top_level` is an overview of all `Area`s). -!!! note "Top toolbar" - The toolbar on top of the window provides the following functionality: +!!! note "Top left toolbar" + The toolbar on top left of the window provides the following functionality: 1. `back`: If currently in an area (opened by the `open` button, see below), navigate back to the `Top level` . This button has the keyboard shortcut `MouseButton4` (or `Esc`). Note: This functionality only works when using the `EnergyModelsGeography` package, as in this example. 2. `open`: Open an area by first selecting the area to open and then clicking this button. This button has the keyboard shortcut `space`. Opening an area can also be accomplished by double clicking the area icon. Note: This functionality only works when using the `EnergyModelsGeography` package, as in this example. 3. `align horz.`: Align selected nodes/areas horizontally. 4. `align vert.`: Align selected nodes/areas vertically. - 5. `save`: Save the coordinates of the `Node`s/`Area`s to file (if there are multiple areas, a file for each area in addition to a file for the `Top level`). The location of these files can be assigned through the `design_path` input parameter to the `GUI` function. - 6. `reset view`: Reset the view to the optimal view based on the current system if the view has been altered. + 5. `save`: Save the coordinates of the `Node`s/`Area`s to file (if there are multiple areas, a file for each area in addition to a file for the `Top level`). The location of these files can be assigned through the `design_path` input parameter to the `GUI` function (this feature has the shortcut `ctrl+s`). + 6. `reset view`: Reset the view to the optimal view based on the current system if the view has been altered (this feature has the shortcut `ctrl+r`). 7. `Exapnd all`: Toggle this to show/hide all components of all `Area`s. - 8. `Period`: Menu for choosing a `StrategicPeriod` for a case. - 9. `Representative period`: Menu for choosing a `RepresentativePeriod` for a case. - 10. `Scenario`: Menu for choosing a `Scenario` for a case. - 11. `Data`: Select the available data to be visualized in the plot area to the bottom right (if a component is selected, the menu will update to contain the available data for this component). + +!!! note "Top right text areas" + The first text area (to the left) shows some tips of using the GUI by defult, but is temporarily changed upon a selection of an EMX object in which information of this object is shown. The second text area (to the right) shows result summary (if available). Note that both these areas are scrollable. + +!!! note "Middle left toolbar" + The toolbar on middle left of the window provides the following functionality: + + 1. `Plot`: Activate one of the three available plots: + - `Strategic`: Strategic period plot + - `Representative`: Representative period plot + - `Operational`: Operational period plot + 2. `Period`: Menu for choosing a `StrategicPeriod` for a case. + 3. `Representative period`: Menu for choosing a `RepresentativePeriod` for a case. + 4. `Scenario`: Menu for choosing a `Scenario` for a case. + 5. `Data`: Select the available data to be visualized in the plot area to the bottom right (if a component is selected, the menu will update to contain the available data for this component). !!! note "Bottom right visualization area" The bottom right fraction of the window shows a visualization of the results associated with the selected available data. It will automatically adjust when altering the different periods/scenarios. @@ -72,15 +87,11 @@ You should then get the following GUI: !!! note "Bottom right toolbar" An additional toolbar on the bottom right is related to the plot area above and has the following functionality: - 1. `Plot`: Activate one of the three available plots: - - `Strategic`: Strategic period plot - - `Representative`: Representative period plot - - `Operational`: Operational period plot - 2. `pin current data`: Pin the latest plotted data, which enables comparing with other data in the same time type. - 3. `remove selected data`: After selecting a plot (left-click the line so it turns green), this button will remove it. - 4. `clear all`: Removes all plots. - 5. Export: Choose which data to export. + 1. `pin current data`: Pin the latest plotted data, which enables comparing with other data in the same time type. + 2. `remove selected data`: After selecting a plot (left-click the line so it turns green), this button will remove it (the button has shortcut `Delete`). You can select multiple plots by holding the `ctrl` key while selecting. + 3. `clear all`: Removes all plots. + 4. Export: Choose which data to export. - `All`: all data (or entire window) - `Plots`: the current active plots - 6. Choose the export format (`REPL` prints the data to the REPL). - 7. `export`: Export the data using the chosen setup. + 5. Choose the export format (`REPL` prints the data to the REPL). + 6. `export`: Export the data using the chosen setup. diff --git a/src/setup_GUI.jl b/src/setup_GUI.jl index 54827ed..72e8ec8 100644 --- a/src/setup_GUI.jl +++ b/src/setup_GUI.jl @@ -96,7 +96,6 @@ function GUI( :backgroundcolor => backgroundcolor, :scale_tot_opex => scale_tot_opex, :scale_tot_capex => scale_tot_capex, - :investment_overview => "", :colormap => colormap, :tol => tol, ) @@ -134,19 +133,20 @@ function GUI( vars[:default_text] = string( "Tips:\n", "Keyboard shortcuts:\n", - "\tctrl+left-click: Select multiple nodes (use arrows to move all selected nodes simultaneously).\n", + "\tctrl+left-click: Select multiple nodes.\n", "\tright-click and drag: to pan\n", "\tscroll wheel: zoom in or out\n", "\tspace: Enter the selected system\n", "\tctrl+s: Save\n", "\tctrl+r: Reset view\n", "\tctrl+w: Close window\n", - "\tEsc (or MouseButton4): Exit the current system and into the parent system\n\n", - "\tholding x while scrolling over plots will zoom in/out in the x-direction\n", - "\tholding y while scrolling over plots will zoom in/out in the y-direction\n\n", - "Left-clicking a component will put information about this component here\n\n", - "Clicking a plot below enables you to pin this plot (hitting the `pin current plot` button) \ - for comparison with other plots. Use the `Delete` button to unpin a selected plot", + "\tEsc (or MouseButton4): Exit the current system and into the parent system\n", + "\tholding x while scrolling over plots will zoom in/out in the x-direction.\n", + "\tholding y while scrolling over plots will zoom in/out in the y-direction.\n\n", + "Left-clicking a component will put information about this component here.\n\n", + "Clicking a plot below enables you to pin this plot (hitting the `pin\n\ + current plot` button) for comparison with other plots.\n", + "Use the `Delete` button to unpin a selected plot.", ) vars[:dragging] = Ref(false) vars[:ctrl_is_pressed] = Ref(false) @@ -209,20 +209,26 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) fig::Figure = Figure(; size=vars[:plot_widths], backgroundcolor=vars[:backgroundcolor]) # Create grid layout structure of the window - gridlayout_taskbar::GridLayout = fig[1, 1:2] = GridLayout() - gridlayout_topology_ax::GridLayout = fig[2:4, 1] = GridLayout(; valign=:top) - gridlayout_info::GridLayout = fig[2, 2] = GridLayout() - gridlayout_results_ax::GridLayout = fig[3, 2] = GridLayout() - gridlayout_results_taskbar::GridLayout = fig[4, 2] = GridLayout() + gridlayout_taskbar::GridLayout = fig[1, 1] = GridLayout() + gridlayout_topology_ax::GridLayout = fig[2:6, 1] = GridLayout() + gridlayout_info::GridLayout = fig[1:2, 2] = GridLayout() + gridlayout_summary::GridLayout = fig[1:2, 3] = GridLayout() + gridlayout_results_taskbar1::GridLayout = fig[3, 2:3] = GridLayout() + gridlayout_results_taskbar2::GridLayout = fig[4, 2:3] = GridLayout() + gridlayout_results_ax::GridLayout = fig[5, 2:3] = GridLayout() + gridlayout_results_taskbar3::GridLayout = fig[6, 2:3] = GridLayout() # Set row sizes of the layout - # Control the relative height of the gridlayout_results_ax (ax for plotting results) + # Control the relative height of the gridlayout_results_ax row heights rowsize!(fig.layout, 1, Fixed(vars[:taskbar_height])) - # Control the relative height of the gridlayout_results_ax (ax for plotting results) - rowsize!(fig.layout, 3, Relative(0.55)) + rowsize!(fig.layout, 3, Fixed(vars[:taskbar_height])) + rowsize!(fig.layout, 4, Fixed(vars[:taskbar_height])) + rowsize!(fig.layout, 5, Relative(0.6)) + rowsize!(fig.layout, 6, Fixed(vars[:taskbar_height])) # Get the current limits of the axis - colsize!(fig.layout, 2, Auto(1)) + colsize!(fig.layout, 1, Relative(0.45)) + colsize!(fig.layout, 2, Relative(0.35)) vars[:ax_aspect_ratio] = vars[:plot_widths][1] / (vars[:plot_widths][2] - vars[:taskbar_height]) / 2 @@ -234,11 +240,7 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) dest::String = "+proj=merc +lon_0=0 +x_0=0 +y_0=0 +a=6378137 +b=6378137 +units=m +no_defs" # Construct the axis from the GeoMakie package ax = GeoMakie.GeoAxis( - gridlayout_topology_ax[1, 1]; - source=source, - dest=dest, - aspect=DataAspect(), - alignmode=Outside(), + gridlayout_topology_ax[1, 1]; source=source, dest=dest, alignmode=Outside() ) if vars[:coarse_coast_lines] # Use low resolution coast lines @@ -278,7 +280,13 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) ) Makie.translate!(ocean, 0, 0, -1) else # The design does not use the EnergyModelsGeography package: Create a simple Makie axis - ax = Axis(gridlayout_topology_ax[1, 1]; aspect=DataAspect(), alignmode=Outside()) + ax = Axis( + gridlayout_topology_ax[1, 1]; + autolimitaspect=true, + alignmode=Outside(), + tellheight=true, + tellwidth=true, + ) end if vars[:hide_topo_ax_decorations] hidedecorations!(ax) @@ -355,6 +363,24 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) # Remove ticks and labels hidedecorations!(ax_info) + # Initiate an axis for displaying summary of the model results + ax_summary::Makie.Axis = Axis( + gridlayout_summary[1, 1]; backgroundcolor=vars[:backgroundcolor] + ) + + # Add text at the top left of the axis domain (to print information of the selected/hovered node/connection) + text!( + ax_summary, + "No model results"; + position=(0.01, 0.99), + align=(:left, :top), + fontsize=vars[:fontsize], + ) + limits!(ax_summary, [0, 1], [0, 1]) + + # Remove ticks and labels + hidedecorations!(ax_summary) + # Add buttons related to the ax object (where the topology is visualized) up_button = Makie.Button( gridlayout_taskbar[1, 1]; label="back", fontsize=vars[:fontsize] @@ -383,104 +409,107 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) ) expand_all_toggle = Makie.Toggle(gridlayout_taskbar[1, 8]; active=vars[:expand_all]) + # Add the following to add flexibility + Makie.Label(gridlayout_taskbar[1, 9], ""; tellwidth=false) + # Add buttons related to the ax_results object (where the optimization results are plotted) Makie.Label( - gridlayout_taskbar[1, 9], + gridlayout_results_taskbar1[1, 1], + "Plot:"; + halign=:right, + fontsize=vars[:fontsize], + justification=:right, + ) + time_menu = Makie.Menu( + gridlayout_results_taskbar1[1, 2]; + options=zip( + ["Strategic", "Representative", "Scenario", "Operational"], + [:results_sp, :results_rp, :results_sc, :results_op], + ), + halign=:left, + width=110 * vars[:fontsize] / 12, + fontsize=vars[:fontsize], + ) + Makie.Label( + gridlayout_results_taskbar1[1, 3], "Period:"; halign=:right, fontsize=vars[:fontsize], justification=:right, ) period_menu = Makie.Menu( - gridlayout_taskbar[1, 10]; + gridlayout_results_taskbar1[1, 4]; options=zip(vars[:periods_labels], periods), default=vars[:periods_labels][1], halign=:left, - width=100 * vars[:fontsize] / 12, + tellwidth=true, + width=nothing, fontsize=vars[:fontsize], ) Makie.Label( - gridlayout_taskbar[1, 11], - "Representative period:"; + gridlayout_results_taskbar1[1, 5], + "Repr. period:"; halign=:right, fontsize=vars[:fontsize], justification=:right, ) representative_period_menu = Makie.Menu( - gridlayout_taskbar[1, 12]; + gridlayout_results_taskbar1[1, 6]; options=zip(vars[:representative_periods_labels], representative_periods), default=vars[:representative_periods_labels][1], halign=:left, - width=100 * vars[:fontsize] / 12, fontsize=vars[:fontsize], ) Makie.Label( - gridlayout_taskbar[1, 13], + gridlayout_results_taskbar1[1, 7], "Scenario:"; halign=:left, fontsize=vars[:fontsize], justification=:left, ) scenario_menu = Makie.Menu( - gridlayout_taskbar[1, 14]; + gridlayout_results_taskbar1[1, 8]; options=zip(vars[:scenarios_labels], scenarios), default=vars[:scenarios_labels][1], halign=:left, - width=100 * vars[:fontsize] / 12, fontsize=vars[:fontsize], ) Makie.Label( - gridlayout_taskbar[1, 15], + gridlayout_results_taskbar2[1, 1], "Data:"; halign=:right, fontsize=vars[:fontsize], justification=:right, ) available_data_menu = Makie.Menu( - gridlayout_taskbar[1, 16]; halign=:left, fontsize=vars[:fontsize], tellwidth=true + gridlayout_results_taskbar2[1, 2]; halign=:left, fontsize=vars[:fontsize] ) # Add the following to add flexibility - Makie.Label(gridlayout_results_taskbar[1, 1], ""; tellwidth=false) + Makie.Label(gridlayout_results_taskbar3[1, 1], ""; tellwidth=false) - # Add task bar over axes[:results] - Makie.Label( - gridlayout_results_taskbar[1, 2], - "Plot:"; - halign=:right, - fontsize=vars[:fontsize], - justification=:right, - ) - time_menu = Makie.Menu( - gridlayout_results_taskbar[1, 3]; - options=zip( - ["Strategic", "Representative", "Scenario", "Operational"], - [:results_sp, :results_rp, :results_sc, :results_op], - ), - halign=:left, - width=120 * vars[:fontsize] / 12, - fontsize=vars[:fontsize], - ) pin_plot_button = Makie.Button( - gridlayout_results_taskbar[1, 4]; label="pin current data", fontsize=vars[:fontsize] + gridlayout_results_taskbar3[1, 2]; + label="pin current data", + fontsize=vars[:fontsize], ) remove_plot_button = Makie.Button( - gridlayout_results_taskbar[1, 5]; + gridlayout_results_taskbar3[1, 3]; label="remove selected data", fontsize=vars[:fontsize], ) clear_all_button = Makie.Button( - gridlayout_results_taskbar[1, 6]; label="clear all", fontsize=vars[:fontsize] + gridlayout_results_taskbar3[1, 4]; label="clear all", fontsize=vars[:fontsize] ) Makie.Label( - gridlayout_results_taskbar[1, 7], + gridlayout_results_taskbar3[1, 5], "Export:"; halign=:right, fontsize=vars[:fontsize], justification=:right, ) axes_menu = Makie.Menu( - gridlayout_results_taskbar[1, 8]; + gridlayout_results_taskbar3[1, 6]; options=["All", "Plots", "Topo"], default="Plots", halign=:left, @@ -488,7 +517,7 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) fontsize=vars[:fontsize], ) export_type_menu = Makie.Menu( - gridlayout_results_taskbar[1, 9]; + gridlayout_results_taskbar3[1, 7]; options=[ "bmp", "tiff", "tif", "jpg", "jpeg", "lp", "mps", "svg", "xlsx", "png", "REPL" ], @@ -498,7 +527,7 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) fontsize=vars[:fontsize], ) export_button = Makie.Button( - gridlayout_results_taskbar[1, 10]; label="export", fontsize=vars[:fontsize] + gridlayout_results_taskbar3[1, 8]; label="export", fontsize=vars[:fontsize] ) # Collect all menus into a dictionary @@ -526,12 +555,17 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) :axes => axes_menu, ) + # Ensure that menus are on top + for menu ∈ values(menus) + translate!(menu.blockscene, 0, 0, vars[:z_translate_components] + 2000) + end + # Collect all toggles into a dictionary toggles::Dict{Symbol,Makie.Toggle} = Dict(:expand_all => expand_all_toggle) # Collect all axes into a dictionary axes::Dict{Symbol,Makie.Block} = Dict( - :topo => ax, :results => ax_results, :info => ax_info + :topo => ax, :results => ax_results, :info => ax_info, :summary => ax_summary ) # Update the title of the figure diff --git a/src/utils_GUI/GUI_utils.jl b/src/utils_GUI/GUI_utils.jl index ff53f77..84371a4 100644 --- a/src/utils_GUI/GUI_utils.jl +++ b/src/utils_GUI/GUI_utils.jl @@ -240,17 +240,7 @@ function initialize_available_data!(gui) for combination ∈ get_combinations(var, i_T) selection = collect(combination) - if isa(var, SparseVars) # Slicing for SparseVars performs worse than the following - field_data = JuMP.Containers.DenseAxisArray( - [ - var[vcat(selection[1:(i_T - 1)], t, selection[i_T:end])...] for - t ∈ periods - ], - periods, - ) - else # For DenseAxisArrays, slicing performs best - field_data = var[vcat(selection[1:(i_T - 1)], :, selection[i_T:end])...] - end + field_data = extract_data_selection(var, selection, i_T, periods) element::Plotable = getfirst( x -> isa(x, Union{EMB.Node,Link,Area,TransmissionMode}), selection ) @@ -268,10 +258,8 @@ function initialize_available_data!(gui) push!(get_available_data(gui)[element], container) end end - end - # Add total quantities - if termination_status(model) == MOI.OPTIMAL + # Add total quantities element = nothing # Calculate total OPEX for each strategic period scale_tot_opex = get_var(gui, :scale_tot_opex) @@ -375,7 +363,7 @@ function initialize_available_data!(gui) get_investment_times(gui, max_inst) # Create investment overview in the information box - investment_overview = "Result summary:\n\n" + investment_overview = "Result summary (no values discounted):\n\n" total_opex = sum(tot_opex_unscaled .* sp_dur) total_capex = sum(tot_capex_unscaled) investment_overview *= "Total operational cost: $(format_number(total_opex))\n" @@ -396,7 +384,7 @@ function initialize_available_data!(gui) investment_overview *= "Investment overview:\n" investment_overview *= inv_overview_components end - gui.vars[:investment_overview] = investment_overview + get_ax(gui, :summary).scene.plots[1][1][] = investment_overview else @warn "Total quantities were not computed as model does not contain a feasible solution" end @@ -417,6 +405,26 @@ function initialize_available_data!(gui) end end end + +""" + extract_data_selection(var::SparseVars, selection::Vector, i_T::Int64, periods::Vector) + +Extract data from `var` having its time dimension at index `i_T` for all time periods in `periods`. +""" +function extract_data_selection( + var::SparseVars, selection::Vector, i_T::Int64, periods::Vector +) + return JuMP.Containers.DenseAxisArray( + [var[vcat(selection[1:(i_T - 1)], t, selection[i_T:end])...] for t ∈ periods], + periods, + ) +end +function extract_data_selection( + var::JuMP.Containers.DenseAxisArray, selection::Vector, i_T::Int64, ::Vector +) + return var[vcat(selection[1:(i_T - 1)], :, selection[i_T:end])...] +end + """ get_JuMP_names(gui::GUI) diff --git a/src/utils_GUI/event_functions.jl b/src/utils_GUI/event_functions.jl index 6484bf1..117db27 100644 --- a/src/utils_GUI/event_functions.jl +++ b/src/utils_GUI/event_functions.jl @@ -109,6 +109,23 @@ function define_event_functions(gui::GUI) # Define the double-click threshold double_click_threshold = Dates.Millisecond(500) # Default value in Windows + ax_info = get_ax(gui, :info) + ax_summary = get_ax(gui, :summary) + + # Alter scrolling functionality in text areas such that it does not zoom but translates in the y-direction + on(events(ax_info).scroll; priority=4) do val + mouse_pos::Tuple{Float64,Float64} = events(ax_info).mouseposition[] + if mouse_within_axis(ax_info, mouse_pos) + scroll_ylim(ax_info, val[2] * 0.1) + return Consume(true) + end + if mouse_within_axis(ax_summary, mouse_pos) + scroll_ylim(ax_summary, val[2] * 0.1) + return Consume(true) + end + return Consume(false) + end + # Handle cases for mousebutton input on(events(get_ax(gui, :topo)).mousebutton; priority=4) do event if event.button == Mouse.left @@ -116,16 +133,11 @@ function define_event_functions(gui::GUI) time_difference = current_click_time - last_click_time[] dragging = get_var(gui, :dragging) if event.action == Mouse.press - # Make sure selections are not removed when left-clicking outside axes[:topo] - mouse_pos::Tuple{Float64,Float64} = events(get_ax(gui, :topo)).mouseposition[] - - origin::Vec2{Int64} = pixelarea(get_ax(gui, :topo).scene)[].origin - widths::Vec2{Int64} = pixelarea(get_ax(gui, :topo).scene)[].widths - mouse_pos_loc::Vec2{Float64} = mouse_pos .- origin + mouse_pos = events(get_ax(gui, :topo)).mouseposition[] # Check if mouseclick is outside the get_ax(gui,:topo) area (and return if so) ctrl_is_pressed = get_var(gui, :ctrl_is_pressed)[] - if all(mouse_pos_loc .> 0.0) && all(mouse_pos_loc .- widths .< 0.0) + if mouse_within_axis(get_ax(gui, :topo), mouse_pos) if !ctrl_is_pressed && !isempty(get_selected_systems(gui)) clear_selection(gui; clear_results=false) end @@ -139,20 +151,23 @@ function define_event_functions(gui::GUI) dragging[] = true return Consume(true) - else + end + + if mouse_within_axis(get_ax(gui, :results), mouse_pos) time_axis = get_menu(gui, :time).selection[] - origin = pixelarea(get_ax(gui, :results).scene)[].origin - widths = pixelarea(get_ax(gui, :results).scene)[].widths - mouse_pos_loc = mouse_pos .- origin - - if all(mouse_pos_loc .> 0.0) && all(mouse_pos_loc .- widths .< 0.0) - if !ctrl_is_pressed && !isempty(get_selected_plots(gui, time_axis)) - clear_selection(gui; clear_topo=false) - end - pick_component!(gui; pick_results_component=true) + if !ctrl_is_pressed && !isempty(get_selected_plots(gui, time_axis)) + clear_selection(gui; clear_topo=false) end + pick_component!(gui; pick_results_component=true) return Consume(false) end + if mouse_within_axis(get_ax(gui, :info), mouse_pos) + return Consume(true) + end + if mouse_within_axis(get_ax(gui, :summary), mouse_pos) + return Consume(true) + end + return Consume(false) elseif event.action == Mouse.release if dragging[] dragging[] = false @@ -166,6 +181,17 @@ function define_event_functions(gui::GUI) return Consume(true) end end + if event.button == Mouse.right + # Disable pan in text areas + mouse_pos = events(get_ax(gui, :topo)).mouseposition[] + if mouse_within_axis(get_ax(gui, :info), mouse_pos) + return Consume(true) + end + if mouse_within_axis(get_ax(gui, :summary), mouse_pos) + return Consume(true) + end + return Consume(false) + end return Consume(false) end diff --git a/src/utils_GUI/info_axis_utils.jl b/src/utils_GUI/info_axis_utils.jl index 4c7bc8b..e941f18 100644 --- a/src/utils_GUI/info_axis_utils.jl +++ b/src/utils_GUI/info_axis_utils.jl @@ -6,12 +6,7 @@ Based on `element` update the text in info box. function update_info_box!(gui::GUI, element; indent::Int64=0) info_box = get_ax(gui, :info).scene.plots[1][1] if isnothing(element) - investment_overview = get_var(gui, :investment_overview) - if isempty(investment_overview) - info_box[] = get_var(gui, :default_text) - else - info_box[] = investment_overview - end + info_box[] = get_var(gui, :default_text) return nothing end if indent == 0 diff --git a/src/utils_GUI/results_axis_utils.jl b/src/utils_GUI/results_axis_utils.jl index 8067555..5c8a704 100644 --- a/src/utils_GUI/results_axis_utils.jl +++ b/src/utils_GUI/results_axis_utils.jl @@ -21,26 +21,26 @@ end """ add_description!( - field::T, + field::TS.TimeProfile, name::String, key_str::String, pre_desc::String, element::Plotable, available_data::Vector{Dict}, gui::GUI, - ) where {T<:TS.TimeProfile} + ) Create a container with a description, and add `container` to `available_data`. """ function add_description!( - field::T, + field::TS.TimeProfile, name::String, key_str::String, pre_desc::String, element::Plotable, available_data::Vector{Dict}, gui::GUI, -) where {T<:TS.TimeProfile} +) container = Dict( :name => name, :is_jump_data => false, diff --git a/src/utils_GUI/topo_axis_utils.jl b/src/utils_GUI/topo_axis_utils.jl index 3acb955..2617e70 100644 --- a/src/utils_GUI/topo_axis_utils.jl +++ b/src/utils_GUI/topo_axis_utils.jl @@ -741,10 +741,6 @@ function adjust_limits!(gui::GUI) vars[:ylimits] = [min_y, max_y] ax = get_ax(gui, :topo) limits!(ax, vars[:xlimits], vars[:ylimits]) - - # Fix the axis limits (needed to avoid resetting limits when adding objects along - # connection lines upon zoom) - ax.autolimitaspect = nothing end """ diff --git a/src/utils_gen/structures_utils.jl b/src/utils_gen/structures_utils.jl index f25917a..2590e9d 100644 --- a/src/utils_gen/structures_utils.jl +++ b/src/utils_gen/structures_utils.jl @@ -28,7 +28,7 @@ function place_nodes_in_circle(n::Int, i::Int, r::Real, xₒ::Real, yₒ::Real) end """ - set_colors(id_to_color_map::Dict{Any,Any}, products::Vector{S}, products_colors::Vector{T}) + set_colors(products::Vector{<:Resource}, id_to_color_map::Dict) Returns a dictionary that completes the dictionary `id_to_color_map` with default color values for standard names (like Power, NG, Coal, CO2) collected from `src/colors.yml`. @@ -36,7 +36,7 @@ for standard names (like Power, NG, Coal, CO2) collected from `src/colors.yml`. Color can be represented as a hex (_i.e._, #a4220b2) or a symbol (_i.e_. :green), but also a string of the identifier for default colors in the `src/colors.yml` file. """ -function set_colors(products::Vector{S}, id_to_color_map::Dict) where {S<:Resource} +function set_colors(products::Vector{<:Resource}, id_to_color_map::Dict) complete_id_to_color_map::Dict = Dict() default_colors::Dict = get_default_colors() for product ∈ products @@ -262,9 +262,7 @@ end Get the colors linked the the resources in `resources` based on the mapping `id_to_color_map`. """ -function get_resource_colors( - resources::Vector{T}, id_to_color_map::Dict{Any,Any} -) where {T<:Resource} +function get_resource_colors(resources::Vector{<:Resource}, id_to_color_map::Dict{Any,Any}) hexColors::Vector{Any} = [id_to_color_map[resource.id] for resource ∈ resources] return [parse(Colorant, hex_color) for hex_color ∈ hexColors] end diff --git a/src/utils_gen/topo_utils.jl b/src/utils_gen/topo_utils.jl index 111356b..441675c 100644 --- a/src/utils_gen/topo_utils.jl +++ b/src/utils_gen/topo_utils.jl @@ -48,11 +48,11 @@ function square_intersection( end """ - l2_norm(x::Vector{T}) where T<:Real + l2_norm(x::Vector{<:Real}) Compute the l2-norm of a vector. """ -function l2_norm(x::Vector{T}) where {T<:Real} +function l2_norm(x::Vector{<:Real}) return sqrt(sum(x .^ 2)) end diff --git a/src/utils_gen/utils.jl b/src/utils_gen/utils.jl index a5e9878..d1fa29b 100644 --- a/src/utils_gen/utils.jl +++ b/src/utils_gen/utils.jl @@ -154,11 +154,11 @@ function format_number(x::Number) end """ - get_max_installed(n, t::Vector{S}) + get_max_installed(n, t::Vector{<:TS.TimeStructure}) Get the maximum capacity installable by an investemnt. """ -function get_max_installed(n::EMB.Node, t::Vector{S}) where {S<:TS.TimeStructure} +function get_max_installed(n::EMB.Node, t::Vector{<:TS.TimeStructure}) if EMI.has_investment(n) time_profile = EMI.max_installed(EMI.investment_data(n, :cap)) return maximum(time_profile[t]) @@ -166,7 +166,7 @@ function get_max_installed(n::EMB.Node, t::Vector{S}) where {S<:TS.TimeStructure return 0.0 end end -function get_max_installed(n::Storage, t::Vector{S}) where {S<:TS.TimeStructure} +function get_max_installed(n::Storage, t::Vector{<:TS.TimeStructure}) if EMI.has_investment(n) storage_data = [ EMI.investment_data(n, :charge), @@ -180,9 +180,7 @@ function get_max_installed(n::Storage, t::Vector{S}) where {S<:TS.TimeStructure} return 0.0 end end -function get_max_installed( - n::EMG.TransmissionMode, t::Vector{S} -) where {S<:TS.TimeStructure} +function get_max_installed(n::EMG.TransmissionMode, t::Vector{<:TS.TimeStructure}) if EMI.has_investment(n) time_profile = EMI.max_installed(EMI.investment_data(n, :cap)) return maximum(time_profile[t]) @@ -190,11 +188,36 @@ function get_max_installed( return 0.0 end end -function get_max_installed( - trans::EMG.Transmission, t::Vector{S} -) where {S<:TS.TimeStructure} +function get_max_installed(trans::EMG.Transmission, t::Vector{<:TS.TimeStructure}) return maximum([get_max_installed(m, t) for m ∈ modes(trans)]) end -function get_max_installed(::Any, ::Vector{S}) where {S<:TS.TimeStructure} +function get_max_installed(::Any, ::Vector{<:TS.TimeStructure}) return 0.0 end + +""" + mouse_within_axis(ax::Makie.AbstractAxis, mouse_pos::Tuple{Float64,Float64}) + +Check if mouse position is within the pixel area of `ax`. +""" +function mouse_within_axis(ax::Makie.AbstractAxis, mouse_pos::Tuple{Float64,Float64}) + origin::Vec2{Int64} = pixelarea(ax.scene)[].origin + widths::Vec2{Int64} = pixelarea(ax.scene)[].widths + mouse_pos_loc::Vec2{Float64} = mouse_pos .- origin + + return all(mouse_pos_loc .> 0.0) && all(mouse_pos_loc .- widths .< 0.0) +end + +""" + scroll_ylim(ax::Makie.AbstractAxis, val::Float64) + +Shift the ylim with `val` units to mimic scrolling feature of an axis `ax`. +""" +function scroll_ylim(ax::Makie.AbstractAxis, val::Float64) + ylim = collect(ax.yaxis.attributes.limits[]) + ylim .+= val + if ylim[2] > 1 + ylim = (0, 1) + end + ylims!(ax, ylim[1], ylim[2]) +end