From 8c05802b68a730d88941074d06c9f760adcb5070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20Vegard=20Ven=C3=A5s?= Date: Tue, 4 Nov 2025 12:26:53 +0100 Subject: [PATCH 1/9] Fix square_intersection function --- src/utils_gen/topo_utils.jl | 87 ++++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 20 deletions(-) diff --git a/src/utils_gen/topo_utils.jl b/src/utils_gen/topo_utils.jl index 6a04583..68e7718 100644 --- a/src/utils_gen/topo_utils.jl +++ b/src/utils_gen/topo_utils.jl @@ -5,30 +5,77 @@ ) where {Tc<:Real, Tx<:Real, Tθ<:Real, TΔ<:Real} Calculate the intersection point between a line starting at `x` and direction described by -`θ` and a square with half side lengths `Δ` centered at center `c`. +`θ`, and a square with half side lengths `Δ` centered at center `c`. If the line does not +intersect the square, the extension of the two facing sides to `x` will be used instead. """ function square_intersection( c::Vector{Tc}, x::Vector{Tx}, θ::Tθ, Δ::TΔ, ) where {Tc<:Real,Tx<:Real,Tθ<:Real,TΔ<:Real} - # Ensure that -π ≤ θ ≤ π - θ = θ > π ? θ - 2π : θ - θ = θ < -π ? θ + 2π : θ - - # Calculate angles at the corers of the square with respect to the point x - θ_se::Tθ = atan(c[2] - x[2] - Δ, c[1] - x[1] + Δ) - θ_ne::Tθ = atan(c[2] - x[2] + Δ, c[1] - x[1] + Δ) - θ_nw::Tθ = atan(c[2] - x[2] + Δ, c[1] - x[1] - Δ) - θ_sw::Tθ = atan(c[2] - x[2] - Δ, c[1] - x[1] - Δ) - - # Return the intersection point - if θ_se <= θ && θ < θ_ne # Facing walls are (:E, :W) - return [c[1] + Δ, x[2] + (c[1] + Δ - x[1]) * tan(θ)] - elseif θ_ne <= θ && θ < θ_nw # Facing walls are (:N, :S) - return [x[1] + (c[2] + Δ - x[2]) / tan(θ), c[2] + Δ] - elseif θ_sw <= θ && θ < θ_se # Facing walls are (:S, :N) - return [x[1] + (c[2] - Δ - x[2]) / tan(θ), c[2] - Δ] - else # Facing walls are (:W, :E) - return [c[1] - Δ, x[2] + (c[1] - Δ - x[1]) * tan(θ)] + # Direction vector from θ + dx = cos(θ) + dy = sin(θ) + + # Square bounds + xmin, xmax = c[1] - Δ, c[1] + Δ + ymin, ymax = c[2] - Δ, c[2] + Δ + + # Parametric line: X = x[1] + t*dx, Y = x[2] + t*dy + ts = Float64[] + # Check intersection with vertical sides (x = xmin and x = xmax) + if abs(dx) > eps() + t1 = (xmin - x[1]) / dx + y1 = x[2] + t1 * dy + if ymin <= y1 <= ymax + push!(ts, t1) + end + t2 = (xmax - x[1]) / dx + y2 = x[2] + t2 * dy + if ymin <= y2 <= ymax + push!(ts, t2) + end + end + # Check intersection with horizontal sides (y = ymin and y = ymax) + if abs(dy) > eps() + t3 = (ymin - x[2]) / dy + x3 = x[1] + t3 * dx + if xmin <= x3 <= xmax + push!(ts, t3) + end + t4 = (ymax - x[2]) / dy + x4 = x[1] + t4 * dx + if xmin <= x4 <= xmax + push!(ts, t4) + end + end + + # Only consider intersections in the positive direction (t > 0) + ts_pos = filter(t -> t > 0, ts) + if !isempty(ts_pos) + tmin = minimum(ts_pos) + return [x[1] + tmin * dx, x[2] + tmin * dy] + else + # No intersection: find which two sides are "facing" x + # Compute vector from x to center + vx, vy = c[1] - x[1], c[2] - x[2] + # Determine which vertical side is facing x + xside = vx > 0 ? xmin : xmax + # Determine which horizontal side is facing x + yside = vy > 0 ? ymin : ymax + + # Compute intersection with the extension of the facing vertical side + t_v = abs(dx) > eps() ? (xside - x[1]) / dx : Inf + # Compute intersection with the extension of the facing horizontal side + t_h = abs(dy) > eps() ? (yside - x[2]) / dy : Inf + + # Choose the closest positive intersection + t_candidates = filter(t -> t > 0, [t_v, t_h]) + if !isempty(t_candidates) + tmin = minimum(t_candidates) + return [x[1] + tmin * dx, x[2] + tmin * dy] + else + @info "No meaningful intersection found." "c = $c" "x = $x" "θ = $θ" "Δ = $Δ" + throw(ArgumentError("No meaningful intersection found.")) + end end end From a76ffae4cc6df0c755bcd8aaeab59d4a31e86b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20Vegard=20Ven=C3=A5s?= Date: Fri, 7 Nov 2025 21:44:16 +0100 Subject: [PATCH 2/9] Use Float32 instead of Number/Real/Float64 for coordinate related computations in topo (also Point2f instead of Tuple and Vector). Remove redundant notify_component and observables (use the @lift macro instead). --- src/datastructures.jl | 7 +- src/setup_GUI.jl | 31 +- src/setup_topology.jl | 23 +- src/utils_GUI/GUI_utils.jl | 4 +- src/utils_GUI/event_functions.jl | 50 +--- src/utils_GUI/topo_axis_utils.jl | 469 +++++++++++++----------------- src/utils_gen/structures_utils.jl | 10 +- src/utils_gen/topo_utils.jl | 136 ++++----- src/utils_gen/utils.jl | 12 +- test/test_interactivity.jl | 18 +- 10 files changed, 325 insertions(+), 435 deletions(-) diff --git a/src/datastructures.jl b/src/datastructures.jl index 11a5644..7c9e696 100644 --- a/src/datastructures.jl +++ b/src/datastructures.jl @@ -119,8 +119,7 @@ energy system designs in Julia. - **`components::Vector{EnergySystemDesign}`** is the components of the system, stored as an array of EnergySystemDesign objects. - **`connections::Vector{Connection}`** are the connections between system parts. -- **`xy::Observable{Tuple{Real,Real}}`** are the coordinates of the system, observed for - changes. +- **`xy::Observable{<:Point2f}`** are the coordinates of the system, observed for changes. - **`icon::String`** is the optional (path to) icons associated with the system, stored as a string. - **`color::Observable{Symbol}`** is the color of the system, observed for changes and @@ -138,7 +137,7 @@ mutable struct EnergySystemDesign <: AbstractGUIObj id_to_icon_map::Dict components::Vector{EnergySystemDesign} connections::Vector - xy::Observable{Tuple{Real,Real}} + xy::Observable{<:Point2f} icon::String color::Observable{Symbol} wall::Observable{Symbol} @@ -152,7 +151,7 @@ function EnergySystemDesign( id_to_icon_map::Dict, components::Vector{EnergySystemDesign}, connections::Vector, - xy::Observable{Tuple{Real,Real}}, + xy::Observable{<:Point2f}, icon::String, color::Observable{Symbol}, wall::Observable{Symbol}, diff --git a/src/setup_GUI.jl b/src/setup_GUI.jl index 8648d21..867903d 100644 --- a/src/setup_GUI.jl +++ b/src/setup_GUI.jl @@ -79,18 +79,18 @@ function GUI( # Set variables vars::Dict{Symbol,Any} = Dict( :title => Observable("top_level"), - :Δh => 0.05, # Sidelength of main box + :Δh => Observable(0.05f0), # Sidelength of main box :coarse_coast_lines => coarse_coast_lines, :Δh_px => 50, # Pixel size of a box for nodes :markersize => 15, # Marker size for arrows in connections - :boundary_add => 0.2, # Relative to the xlim/ylim-dimensions, expand the axis + :boundary_add => 0.2f0, # Relative to the xlim/ylim-dimensions, expand the axis :line_sep_px => 2, # Separation (in px) between lines for connections :connection_linewidth => 2, # line width of connection lines :ax_aspect_ratio => 1.0, # Aspect ratio for the topology plotting area :fontsize => fontsize, # General font size (in px) :linewidth => 1.2, # Width of the line around boxes :parent_scaling => 1.1, # Scale for enlargement of boxes around main boxes for nodes for parent systems - :icon_scale => 0.9, # scale icons w.r.t. the surrounding box in fraction of Δh + :icon_scale => 0.9f0, # scale icons w.r.t. the surrounding box in fraction of Δh :two_way_sep_px => 10, # No pixels between set of lines for nodes having connections both ways :selection_color => :green2, # Colors for box boundaries when selection objects :investment_lineStyle => Linestyle([1.0, 1.5, 2.0, 2.5] .* 5), # linestyle for investment connections and box boundaries for nodes @@ -134,16 +134,16 @@ function GUI( vars[:hide_topo_ax_decorations] = hide_topo_ax_decorations vars[:expand_all] = expand_all - vars[:xlimits] = Vector{Float64}([0.0, 1.0]) - vars[:ylimits] = Vector{Float64}([0.0, 1.0]) + vars[:xlimits] = Vector{Float32}([0.0f0, 1.0f0]) + vars[:ylimits] = Vector{Float32}([0.0f0, 1.0f0]) - vars[:topo_title_loc_x] = Observable(0.0) - vars[:topo_title_loc_y] = Observable(0.0) + vars[:topo_title_loc_x] = Observable(0.0f0) + vars[:topo_title_loc_y] = Observable(0.0f0) # Create iterables for plotting objects in layers (z-direction) such that nodes are # neatly placed on top of each other and lines are beneath nodes - vars[:z_translate_lines] = 1000 - vars[:z_translate_components] = 5000 + vars[:z_translate_lines] = 1000.0f0 + vars[:z_translate_components] = 5000.0f0 vars[:selected_systems] = [] @@ -411,7 +411,7 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) text!( ax_info, vars[:default_text]; - position = (0.01, 0.99), + position = (0.01f0, 0.99f0), align = (:left, :top), fontsize = vars[:fontsize], ) @@ -429,7 +429,7 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) text!( ax_summary, "No model results"; - position = (0.01, 0.99), + position = (0.01f0, 0.99f0), align = (:left, :top), fontsize = vars[:fontsize], ) @@ -626,7 +626,7 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) # Ensure that menus are on top for menu ∈ values(menus) - translate!(menu.blockscene, 0, 0, vars[:z_translate_components] + 2000) + translate!(menu.blockscene, 0.0f0, 0.0f0, vars[:z_translate_components] + 2000.0f0) end # Collect all toggles into a dictionary @@ -645,7 +645,12 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) text = vars[:title], fontsize = vars[:fontsize], ) - Makie.translate!(vars[:topo_title_obj], 0, 0, vars[:z_translate_components] + 999) + Makie.translate!( + vars[:topo_title_obj], + 0.0f0, + 0.0f0, + vars[:z_translate_components] + 999.0f0, + ) return fig, buttons, menus, toggles, axes, legends end diff --git a/src/setup_topology.jl b/src/setup_topology.jl index 3714000..05d9ee1 100644 --- a/src/setup_topology.jl +++ b/src/setup_topology.jl @@ -11,8 +11,8 @@ the function initializes the `EnergySystemDesign`. - **`design_path::String=""`** is a file path or identifier related to the design. - **`id_to_color_map::Dict`** is a dictionary of resources and their assigned colors. - **`id_to_icon_map::Dict`** is a dictionary of nodes and their assigned icons. -- **`x::Real=0.0`** is the initial x-coordinate of the system. -- **`y::Real=0.0`** is the initial y-coordinate of the system. +- **`x::Float32=0.0f0`** is the initial x-coordinate of the system. +- **`y::Float32=0.0f0`** is the initial y-coordinate of the system. - **`icon::String=""`** is the optional (path to) icons associated with the system, stored as a string. - **`parent::Union{Symbol, Nothing}=nothing`** is a parent reference or indicator. @@ -27,8 +27,8 @@ function EnergySystemDesign( design_path::String = "", id_to_color_map::Dict = Dict(), id_to_icon_map::Dict = Dict(), - x::Real = 0.0, - y::Real = 0.0, + x::Float32 = 0.0f0, + y::Float32 = 0.0f0, icon::String = "", ) # Create the path to the file where existing design is stored (if any) @@ -53,7 +53,7 @@ function EnergySystemDesign( # Create an observable for the coordinate xy that can be inherited as the coordinate # parent_xy - xy::Observable{Tuple{Real,Real}} = Observable((x, y)) + xy::Observable{Point2f} = Observable(Point2f(x, y)) # Create an iterator for the current system elements = get_children(system) @@ -73,23 +73,22 @@ function EnergySystemDesign( # Extract x and y coordinates from file, or from structure or add defaults if haskey(system_info, "x") && haskey(system_info, "y") - x = system_info["x"] - y = system_info["y"] + x = Float32(system_info["x"]) + y = Float32(system_info["y"]) elseif isa(system, SystemGeo) if hasproperty(element, :lon) && hasproperty(element, :lat) # assigning longitude and latitude - x = element.lon - y = element.lat + x = Float32(element.lon) + y = Float32(element.lat) else @error "Missing lon and/or lat coordinates" end else if element == get_ref_element(system) - x = parent_x - y = parent_y + x, y = parent_x, parent_y else # place nodes in a circle around the parents availability node x, y = place_nodes_in_circle( - nodes_count, current_node, 1, parent_x, parent_y, + nodes_count, current_node, 1.0f0, parent_x, parent_y, ) current_node += 1 end diff --git a/src/utils_GUI/GUI_utils.jl b/src/utils_GUI/GUI_utils.jl index a9b4219..53b06a5 100644 --- a/src/utils_GUI/GUI_utils.jl +++ b/src/utils_GUI/GUI_utils.jl @@ -15,7 +15,7 @@ function toggle_selection_color!(gui::GUI, selection::Connection, selected::Bool plots = selection.plots if selected for plot ∈ plots - for plot_sub ∈ plot[] + for plot_sub ∈ plot plot_sub.color = get_selection_color(gui) end end @@ -23,7 +23,7 @@ function toggle_selection_color!(gui::GUI, selection::Connection, selected::Bool colors::Vector{RGB} = selection.colors no_colors::Int64 = length(colors) for plot ∈ plots - for (i, plot_sub) ∈ enumerate(plot[]) + for (i, plot_sub) ∈ enumerate(plot) plot_sub.color = colors[((i-1)%no_colors)+1] end end diff --git a/src/utils_GUI/event_functions.jl b/src/utils_GUI/event_functions.jl index 9caaa78..b6c9f33 100644 --- a/src/utils_GUI/event_functions.jl +++ b/src/utils_GUI/event_functions.jl @@ -4,19 +4,6 @@ Define event functions (handling button clicks, plot updates, etc.) for the GUI `gui`. """ function define_event_functions(gui::GUI) - # Create a function that notifies all components (and thus updates graphics - # when the observables are notified) - notify_components = () -> begin - for component ∈ get_root_design(gui).components - notify(component.xy) - if !isempty(component.components) - for sub_component ∈ component.components - notify(sub_component.xy) - end - end - end - end - ax_topo = get_ax(gui, :topo) ax_results = get_ax(gui, :results) ax_info = get_ax(gui, :info) @@ -30,7 +17,6 @@ function define_event_functions(gui::GUI) get_vars(gui)[:xlimits] = [origin[1], origin[1] + widths[1]] get_vars(gui)[:ylimits] = [origin[2], origin[2] + widths[2]] update_distances!(gui) - notify_components() get_var(gui, :topo_title_loc_x)[] = origin[1] + widths[1] / 100 get_var(gui, :topo_title_loc_y)[] = origin[2] + widths[2] - widths[2] / 100 - @@ -71,20 +57,15 @@ function define_event_functions(gui::GUI) # move a component(s) using the arrow keys # get changes - change::Tuple{Float64,Float64} = get_change(gui, Val(event.key)) + change::Point2f = get_change(gui, Val(event.key)) # check if any changes where made - if change != (0.0, 0.0) + if change != Point2f(0.0f0, 0.0f0) for sub_design ∈ get_selected_systems(gui) - xc::Real = sub_design.xy[][1] - yc::Real = sub_design.xy[][2] - - sub_design.xy[] = (xc + change[1], yc + change[2]) + sub_design.xy[] += change update_sub_system_locations!(sub_design, change) end - - notify_components() end elseif Int(event.key) == 256 # Esc used to move up a level in the topology notify(get_button(gui, :up).clicks) @@ -123,13 +104,13 @@ function define_event_functions(gui::GUI) # 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[] + mouse_pos::Tuple{Float32,Float32} = events(ax_info).mouseposition[] if mouse_within_axis(ax_info, mouse_pos) - scroll_ylim(ax_info, val[2] * 0.1) + scroll_ylim(ax_info, val[2] * 0.1f0) return Consume(true) end if mouse_within_axis(ax_summary, mouse_pos) - scroll_ylim(ax_summary, val[2] * 0.1) + scroll_ylim(ax_summary, val[2] * 0.1f0) return Consume(true) end if mouse_within_axis(ax_results, mouse_pos) @@ -146,7 +127,7 @@ function define_event_functions(gui::GUI) time_difference = current_click_time - last_click_time[] dragging = get_var(gui, :dragging) if event.action == Mouse.press - mouse_pos = events(ax_topo).mouseposition[] + mouse_pos::Tuple{Float32,Float32} = events(ax_topo).mouseposition[] # Check if mouseclick is outside the ax_topo area (and return if so) ctrl_is_pressed = get_var(gui, :ctrl_is_pressed)[] @@ -216,20 +197,20 @@ function define_event_functions(gui::GUI) # Handle mouse movement on(events(ax_topo).mouseposition; priority = 2) do mouse_pos # priority ≥ 2 in order to suppress GLMakie left-click and drag zoom feature if get_var(gui, :dragging)[] - origin::Vec2{Int64} = pixelarea(ax_topo.scene)[].origin - widths::Vec2{Int64} = pixelarea(ax_topo.scene)[].widths - mouse_pos_loc::Vec2{Float64} = mouse_pos .- origin + origin::Point2f = pixelarea(ax_topo.scene)[].origin + widths::Point2f = pixelarea(ax_topo.scene)[].widths + mouse_pos_loc::Point2f = mouse_pos .- origin - xy_widths::Vec2 = ax_topo.finallimits[].widths - xy_origin::Vec2 = ax_topo.finallimits[].origin + xy_widths::Point2f = ax_topo.finallimits[].widths + xy_origin::Point2f = ax_topo.finallimits[].origin - xy::Vec2{Float64} = xy_origin .+ mouse_pos_loc .* xy_widths ./ widths + xy::Point2f = xy_origin .+ mouse_pos_loc .* xy_widths ./ widths selected_systems = get_selected_systems(gui) if !isempty(selected_systems) && isa(selected_systems[1], EnergySystemDesign) # Only nodes/area can be moved (connections will update correspondinlgy) sub_design::EnergySystemDesign = selected_systems[1] - update_sub_system_locations!(sub_design, Tuple(xy .- sub_design.xy[])) - sub_design.xy[] = Tuple(xy) + update_sub_system_locations!(sub_design, xy .- sub_design.xy[]) + sub_design.xy[] = Point2f(xy) end return Consume(true) end @@ -364,7 +345,6 @@ function define_event_functions(gui::GUI) get_vars(gui)[:expand_all] = val plot_design!(gui, get_design(gui); expand_all = val) update_distances!(gui) - notify_components() return Consume(false) end diff --git a/src/utils_GUI/topo_axis_utils.jl b/src/utils_GUI/topo_axis_utils.jl index 279c4e4..cad9af0 100644 --- a/src/utils_GUI/topo_axis_utils.jl +++ b/src/utils_GUI/topo_axis_utils.jl @@ -6,18 +6,18 @@ Convert `pixel_size` to data widths (in x- and y-direction) in design object `gu function pixel_to_data(gui::GUI, pixel_size::Real) # Calculate the range in data coordinates vars = get_vars(gui) - x_range::Float64 = vars[:xlimits][2] - vars[:xlimits][1] - y_range::Float64 = vars[:ylimits][2] - vars[:ylimits][1] + x_range::Float32 = vars[:xlimits][2] - vars[:xlimits][1] + y_range::Float32 = vars[:ylimits][2] - vars[:ylimits][1] # Get the widths of the axis plot_widths::Vec2{Int64} = viewport(get_ax(gui, :topo).scene)[].widths # Calculate the conversion factor - x_factor::Float64 = x_range / plot_widths[1] - y_factor::Float64 = y_range / plot_widths[2] + x_factor::Float32 = x_range / plot_widths[1] + y_factor::Float32 = y_range / plot_widths[2] # Convert pixel size to data coordinates - return (pixel_size * x_factor, pixel_size * y_factor) + return Point2f(pixel_size * x_factor, pixel_size * y_factor) end """ @@ -27,12 +27,12 @@ Find the minimum distance between the elements in the design object `gui` and up that neighbouring icons do not overlap. """ function update_distances!(gui::GUI) - min_d::Float64 = Inf + min_d::Float32 = Inf design = get_design(gui) components = get_components(design) if length(components) > 1 for component ∈ components - d::Float64 = minimum([ + d::Float32 = minimum([ l2_norm(collect(get_xy(component)[] .- get_xy(component2)[])) for component2 ∈ components if component != component2 ]) @@ -56,12 +56,10 @@ function new_global_delta_h(gui::GUI) axes = get_axes(gui) xyWidths::Vec = axes[:topo].finallimits[].widths plot_widths::Vec2{Int64} = viewport(axes[:topo].scene)[].widths - vars[:Δh] = maximum([ - maximum(Vector(0.5 * vars[:Δh_px] * xyWidths ./ plot_widths)), - minimum([ - minimum(Vector(vars[:Δh_px] * xyWidths ./ plot_widths)), - vars[:minimum_distance] / 2, # Do this to avoid overlapping squares - ]), + min_distance_x = vars[:minimum_distance] / sqrt(2) # the minimum distance to avoid overlapping squares + vars[:Δh][] = minimum([ + minimum(Vector(vars[:Δh_px] * xyWidths ./ plot_widths)), + min_distance_x * 0.90f0, # separate the squares sufficiently ]) end @@ -71,11 +69,11 @@ end Handle different keyboard inputs (events) and return changes in x, y coordinates in the design object `gui`. """ -get_change(::GUI, ::Val) = (0.0, 0.0) -get_change(gui::GUI, ::Val{Keyboard.up}) = (0.0, +get_var(gui, :Δh) / 5) -get_change(gui::GUI, ::Val{Keyboard.down}) = (0.0, -get_var(gui, :Δh) / 5) -get_change(gui::GUI, ::Val{Keyboard.left}) = (-get_var(gui, :Δh) / 5, 0.0) -get_change(gui::GUI, ::Val{Keyboard.right}) = (+get_var(gui, :Δh) / 5, 0.0) +get_change(::GUI, ::Val) = Point2f(0.0f0, 0.0f0) +get_change(gui::GUI, ::Val{Keyboard.up}) = Point2f(0.0f0, +get_var(gui, :Δh)[] / 5) +get_change(gui::GUI, ::Val{Keyboard.down}) = Point2f(0.0f0, -get_var(gui, :Δh)[] / 5) +get_change(gui::GUI, ::Val{Keyboard.left}) = Point2f(-get_var(gui, :Δh)[] / 5, 0.0f0) +get_change(gui::GUI, ::Val{Keyboard.right}) = Point2f(+get_var(gui, :Δh)[] / 5, 0.0f0) """ align(gui::GUI, align::Symbol) @@ -88,8 +86,8 @@ The following values are allowed - `:vertical` for vertical alignment. """ function align(gui::GUI, align::Symbol) - xs::Vector{Real} = Real[] - ys::Vector{Real} = Real[] + xs::Vector{Float32} = Float32[] + ys::Vector{Float32} = Float32[] for sub_design ∈ get_selected_systems(gui) if isa(sub_design, EnergySystemDesign) x, y = sub_design.xy[] @@ -99,7 +97,7 @@ function align(gui::GUI, align::Symbol) end # Use the average of the components as the basis of the translated coordinate - z::Real = if align == :horizontal + z::Float32 = if align == :horizontal sum(ys) / length(ys) elseif align == :vertical sum(xs) / length(xs) @@ -153,7 +151,7 @@ function plot_design!( for component ∈ get_components(design), plot ∈ component.plots plot.visible = visible end - for connection ∈ get_connections(design), plots ∈ get_plots(connection), plot ∈ plots[] + for connection ∈ get_connections(design), plots ∈ get_plots(connection), plot ∈ plots plot.visible = visible end end @@ -177,7 +175,7 @@ function connect!(gui::GUI, design::EnergySystemDesign) connections, ) on(component.xy; priority = 4) do _ - angles::Vector{Float64} = vcat( + angles::Vector{Float32} = vcat( [ angle(component, linked_component.from) for linked_component ∈ linked_to_component @@ -187,10 +185,10 @@ function connect!(gui::GUI, design::EnergySystemDesign) linked_component ∈ linked_from_component ], ) - min_angle_diff::Vector{Float64} = fill(Inf, 4) + min_angle_diff::Vector{Float32} = fill(Inf, 4) for i ∈ eachindex(min_angle_diff) for angle ∈ angles - Δθ = angle_difference(angle, (i - 1) * π / 2) + Δθ::Float32 = angle_difference(angle, (i - 1) * Float32(π) / 2) if min_angle_diff[i] > Δθ min_angle_diff[i] = Δθ end @@ -228,8 +226,8 @@ function connect!(gui::GUI, connection::Connection, two_way::Bool) no_colors::Int64 = length(colors) # Create an arrow to highlight the direction of the energy flow - l::Float64 = 1.0 # length of the arrow - t::Float64 = 0.5 # half of the thickness of the arrow + l::Float32 = 1.0f0 # length of the arrow + t::Float32 = 0.5f0 # half of the thickness of the arrow arrow_parts::Vector{Makie.BezierPath} = Vector{Makie.BezierPath}(undef, no_colors) for i ∈ range(1, no_colors) arrow_parts[i] = Makie.BezierPath([ @@ -241,126 +239,99 @@ function connect!(gui::GUI, connection::Connection, two_way::Bool) end # Allocate and store objects - line_connections::Observable{Vector{Any}} = Observable(Vector{Any}(undef, 0)) - arrow_heads::Observable{Vector{Any}} = Observable(Vector{Any}(undef, 0)) + line_connections::Vector{Any} = Vector{Any}(undef, 0) + arrow_heads::Vector{Any} = Vector{Any}(undef, 0) push!(get_plots(connection), line_connections) push!(get_plots(connection), arrow_heads) linestyle = get_linestyle(gui, connection) - # Create function to be run on changes in connection.from and connection.to - update = - () -> begin - markersize_lengths::Tuple{Float64,Float64} = pixel_to_data( - gui, get_var(gui, :markersize), - ) - xy_1::Vector{Real} = collect(connection.from.xy[]) - xy_2::Vector{Real} = collect(connection.to.xy[]) - - for i ∈ 1:length(line_connections[]) - line_connections[][i].visible = false - end - for i ∈ 1:length(arrow_heads[]) - arrow_heads[][i].visible = false - end + from_xy = connection.from.xy + to_xy = connection.to.xy + Δh = get_var(gui, :Δh) - lines_shift::Tuple{Float64,Float64} = + for j ∈ 1:no_colors + triple = @lift begin + lines_shift::Point2f = pixel_to_data(gui, get_var(gui, :connection_linewidth)) .+ pixel_to_data(gui, get_var(gui, :line_sep_px)) - two_way_sep::Tuple{Float64,Float64} = pixel_to_data( - gui, get_var(gui, :two_way_sep_px), + two_way_sep::Point2f = pixel_to_data(gui, get_var(gui, :two_way_sep_px)) + markersize_lengths::Point2f = pixel_to_data( + gui, get_var(gui, :markersize), ) - θ::Float64 = atan(xy_2[2] - xy_1[2], xy_2[1] - xy_1[1]) - cosθ::Float64 = cos(θ) - sinθ::Float64 = sin(θ) - cosϕ::Float64 = -sinθ # where ϕ = θ+π/2 - sinϕ::Float64 = cosθ - Δ::Float64 = get_var(gui, :Δh) / 2 # half width of a box + xy_1::Point2f = $from_xy + xy_2::Point2f = $to_xy + + θ::Float32 = atan(xy_2[2] - xy_1[2], xy_2[1] - xy_1[1]) + cosθ::Float32 = cos(θ) + sinθ::Float32 = sin(θ) + cosϕ::Float32 = -sinθ # where ϕ = θ+π/2 + sinϕ::Float32 = cosθ + dirϕ::Point2f = Point2f(cosϕ, sinϕ) + dirθ::Point2f = Point2f(cosθ, sinθ) + + Δ::Float32 = $Δh / 2 # half width of a box if !isempty(get_components(connection.from)) Δ *= get_var(gui, :parent_scaling) end - for j ∈ 1:no_colors - xy_start::Vector{Float64} = copy(xy_1) - xy_end::Vector{Float64} = copy(xy_2) - xy_midpoint::Vector{Float64} = copy(xy_2) # The midpoint of the end of all lines (for arrow head) - if two_way - xy_start[1] += (two_way_sep[1] / 2 + lines_shift[1] * (j - 1)) * cosϕ - xy_start[2] += - (two_way_sep[2] / 2 + lines_shift[2] * (j - 1)) * sinϕ - xy_end[1] += (two_way_sep[1] / 2 + lines_shift[1] * (j - 1)) * cosϕ - xy_end[2] += (two_way_sep[2] / 2 + lines_shift[2] * (j - 1)) * sinϕ - xy_midpoint[1] += - (two_way_sep[1] / 2 + lines_shift[1] * (no_colors - 1) / 2) * cosϕ - xy_midpoint[2] += - (two_way_sep[2] / 2 + lines_shift[2] * (no_colors - 1) / 2) * sinϕ - else - xy_start[1] += lines_shift[1] * (j - 1) * cosϕ - xy_start[2] += lines_shift[2] * (j - 1) * sinϕ - xy_end[1] += lines_shift[1] * (j - 1) * cosϕ - xy_end[2] += lines_shift[2] * (j - 1) * sinϕ - xy_midpoint[1] += lines_shift[1] * (no_colors - 1) / 2 * cosϕ - xy_midpoint[2] += lines_shift[2] * (no_colors - 1) / 2 * sinϕ - end - xy_start = square_intersection(xy_1, xy_start, θ, Δ) - xy_end = square_intersection(xy_2, xy_end, θ + π, Δ) - xy_midpoint = square_intersection(xy_2, xy_midpoint, θ + π, Δ) - parm::Float64 = - -xy_start[1] * cosθ - xy_start[2] * sinθ + - xy_midpoint[1] * cosθ + - xy_midpoint[2] * sinθ - minimum(markersize_lengths) - xs::Vector{Float64} = [xy_start[1], parm * cosθ + xy_start[1]] - ys::Vector{Float64} = [xy_start[2], parm * sinθ + xy_start[2]] - - if length(arrow_heads[]) < j - sctr = scatter!( - get_axes(gui)[:topo], - xy_midpoint[1], - xy_midpoint[2]; - marker = arrow_parts[j], - markersize = get_var(gui, :markersize), - rotation = θ, - color = colors[j], - inspectable = false, - ) - lns = lines!( - get_axes(gui)[:topo], - xs, - ys; - color = colors[j], - linewidth = get_var(gui, :connection_linewidth), - linestyle = linestyle[j], - inspector_label = (self, i, p) -> get_hover_string(connection), - inspectable = true, - ) - Makie.translate!(sctr, 0, 0, get_var(gui, :z_translate_lines)) - get_vars(gui)[:z_translate_lines] += 1 - Makie.translate!(lns, 0, 0, get_var(gui, :z_translate_lines)) - get_vars(gui)[:z_translate_lines] += 1 - push!(arrow_heads[], sctr) - push!(line_connections[], lns) - else - arrow_heads[][j][1][] = - [Point{2,Float64}(xy_midpoint[1], xy_midpoint[2])] - arrow_heads[][j][:rotation] = θ - arrow_heads[][j].visible = true - line_connections[][j][1][] = [ - Point{2,Float64}(x, y) for (x, y) ∈ zip(xs, ys) - ] - line_connections[][j].visible = true - end - line_connections[][j].kw[:EMGUI_obj] = connection - arrow_heads[][j].kw[:EMGUI_obj] = connection - end - end + xy_start::Point2f = xy_1 + xy_end::Point2f = xy_2 + xy_midpoint::Point2f = xy_2 # The midpoint of the end of all lines (for arrow head) + + xy_start += (j - 1) * lines_shift .* dirϕ + xy_end += (j - 1) * lines_shift .* dirϕ + xy_midpoint += (no_colors - 1) / 2 * lines_shift .* dirϕ - # If components changes position, so must the connections - for component ∈ [connection.from, connection.to] - on(component.xy; priority = 3) do _ - if component.plots[1].visible[] - update() + if two_way + xy_start += two_way_sep / 2 .* dirϕ + xy_end += two_way_sep / 2 .* dirϕ + xy_midpoint += two_way_sep / 2 .* dirϕ end + xy_start = square_intersection(xy_1, xy_start, θ, Δ) + xy_end = square_intersection(xy_2, xy_end, θ + π, Δ) + xy_midpoint = square_intersection(xy_2, xy_midpoint, θ + π, Δ) + parm::Float32 = + -xy_start[1] * cosθ - xy_start[2] * sinθ + + xy_midpoint[1] * cosθ + + xy_midpoint[2] * sinθ - minimum(markersize_lengths) + pts_line = Point2f[xy_start, parm*dirθ+xy_start] + + # return the objects into a tuple Observable + (xy_midpoint, θ, pts_line) end + xy_midpoint_j = @lift $triple[1] + θⱼ = @lift $triple[2] + pts_line_j = @lift $triple[3] + + sctr = scatter!( + get_axes(gui)[:topo], + xy_midpoint_j; + marker = arrow_parts[j], + markersize = get_var(gui, :markersize), + rotation = θⱼ, + color = colors[j], + inspectable = false, + ) + lns = lines!( + get_axes(gui)[:topo], + pts_line_j; + color = colors[j], + linewidth = get_var(gui, :connection_linewidth), + linestyle = linestyle[j], + inspector_label = (self, i, p) -> get_hover_string(connection), + inspectable = true, + ) + Makie.translate!(sctr, 0, 0, get_var(gui, :z_translate_lines)) + get_vars(gui)[:z_translate_lines] += 1 + Makie.translate!(lns, 0, 0, get_var(gui, :z_translate_lines)) + get_vars(gui)[:z_translate_lines] += 1 + + sctr.kw[:EMGUI_obj] = connection + lns.kw[:EMGUI_obj] = connection + + push!(arrow_heads, sctr) + push!(line_connections, lns) end end @@ -432,86 +403,51 @@ Draw a box for EnergySystemDesign `design` and it's appearance, including style, function draw_box!(gui::GUI, design::EnergySystemDesign) linestyle::Union{Symbol,Makie.Linestyle} = get_linestyle(gui, design) + Δh = get_var(gui, :Δh) + xy = design.xy + # if the design has components, draw an enlarged box around it. if !isempty(get_components(design)) - xo2::Observable{Vector{Real}} = Observable(zeros(5)) - yo2::Observable{Vector{Real}} = Observable(zeros(5)) - vertices2::Vector{Tuple{Real,Real}} = [ - (x, y) for (x, y) ∈ zip(xo2[][1:(end-1)], yo2[][1:(end-1)]) - ] + # Build the rectangle path from the observables + rect = @lift begin + Δ = $Δh * get_var(gui, :parent_scaling) + Rect2f($xy .- Point2f(Δ/2, Δ/2), Point2f(Δ, Δ)) + end white_rect2 = poly!( - get_axes(gui)[:topo], vertices2; color = :white, strokewidth = 0, + get_axes(gui)[:topo], + rect; + color = :white, inspectable = false, + strokewidth = get_var(gui, :linewidth), + strokecolor = design.color, + linestyle = linestyle, ) # Create a white background rectangle to hide lines from connections - Makie.translate!(white_rect2, 0, 0, get_var(gui, :z_translate_components)) - get_vars(gui)[:z_translate_components] += 1 - push!(design.plots, white_rect2) - # observe changes in design coordinates and update enlarged box position - on(design.xy; priority = 3) do val - x = val[1] - y = val[2] - - xo2[], yo2[] = box(x, y, get_var(gui, :Δh) / 2 * get_var(gui, :parent_scaling)) - white_rect2[1] = [ - (x, y) for (x, y) ∈ zip(xo2[][1:(end-1)], yo2[][1:(end-1)]) - ] - end - - box_boundary2 = lines!( - get_axes(gui)[:topo], - xo2, - yo2; - color = design.color, - linewidth = get_var(gui, :linewidth), - linestyle = :solid, - inspectable = false, - ) - Makie.translate!(box_boundary2, 0, 0, get_var(gui, :z_translate_components)) - get_vars(gui)[:z_translate_components] += 1 - push!(design.plots, box_boundary2) - box_boundary2.kw[:EMGUI_obj] = design + Makie.translate!(white_rect2, 0.0f0, 0.0f0, get_var(gui, :z_translate_components)) + get_vars(gui)[:z_translate_components] += 1.0f0 + push!(design.plots, white_rect2) white_rect2.kw[:EMGUI_obj] = design end - xo::Observable{Vector{Real}} = Observable(zeros(5)) - yo::Observable{Vector{Real}} = Observable(zeros(5)) - vertices::Vector{Tuple{Real,Real}} = [ - (x, y) for (x, y) ∈ zip(xo[][1:(end-1)], yo[][1:(end-1)]) - ] + # Build the rectangle path from the observables + rect = @lift Rect2f($xy .- Point2f($Δh/2, $Δh/2), Point2f($Δh, $Δh)) + white_rect = poly!( - get_axes(gui)[:topo], vertices; color = :white, strokewidth = 0, + get_axes(gui)[:topo], + rect; + color = :white, inspectable = false, + strokewidth = get_var(gui, :linewidth), + strokecolor = design.color, + linestyle = linestyle, ) # Create a white background rectangle to hide lines from connections + add_inspector_to_poly!(white_rect, (self, i, p) -> get_hover_string(design)) - Makie.translate!(white_rect, 0, 0, get_var(gui, :z_translate_components)) - get_vars(gui)[:z_translate_components] += 1 + Makie.translate!(white_rect, 0.0f0, 0.0f0, get_var(gui, :z_translate_components)) + get_vars(gui)[:z_translate_components] += 1.0f0 push!(design.plots, white_rect) - - # Observe changes in design coordinates and update box position - on(design.xy; priority = 3) do val - x::Real = val[1] - y::Real = val[2] - - xo[], yo[] = box(x, y, get_var(gui, :Δh) / 2) - white_rect[1] = [(x, y) for (x, y) ∈ zip(xo[][1:(end-1)], yo[][1:(end-1)])] - end - - box_boundary = lines!( - get_axes(gui)[:topo], - xo, - yo; - color = design.color, - linewidth = get_var(gui, :linewidth), - linestyle = linestyle, - inspectable = false, - ) - Makie.translate!(box_boundary, 0, 0, get_var(gui, :z_translate_components)) - get_vars(gui)[:z_translate_components] += 1 - push!(design.plots, box_boundary) - box_boundary.kw[:EMGUI_obj] = design white_rect.kw[:EMGUI_obj] = design end @@ -521,26 +457,6 @@ end Draw an icon for EnergySystemDesign `design`. """ function draw_icon!(gui::GUI, design::EnergySystemDesign) - xo::Observable{Vector{Real}} = Observable([0.0, 0.0]) - yo::Observable{Vector{Real}} = Observable([0.0, 0.0]) - xo_image = Observable(0.0 .. 0.0) - yo_image = Observable(0.0 .. 0.0) - on(design.xy; priority = 3) do val - x::Real = val[1] - y::Real = val[2] - - xo[] = [ - x - get_var(gui, :Δh) * get_var(gui, :icon_scale) / 2, - x + get_var(gui, :Δh) * get_var(gui, :icon_scale) / 2, - ] - yo[] = [ - y - get_var(gui, :Δh) * get_var(gui, :icon_scale) / 2, - y + get_var(gui, :Δh) * get_var(gui, :icon_scale) / 2, - ] - xo_image[] = xo[][1] .. xo[][2] - yo_image[] = yo[][1] .. yo[][2] - end - if isempty(design.icon) # No path to an icon has been found node::EMB.Node = get_ref_element(design) @@ -560,8 +476,8 @@ function draw_icon!(gui::GUI, design::EnergySystemDesign) for (j, colors) ∈ enumerate([colors_input, colors_output]) no_colors::Int64 = length(colors) for (i, color) ∈ enumerate(colors) - θᵢ::Float64 = 0 - θᵢ₊₁::Float64 = 0 + θᵢ::Float32 = 0.0f0 + θᵢ₊₁::Float32 = 0.0f0 # Check if node is a NetworkNode (if so, devide disc into two where # left side is for input and right side is for output) @@ -572,7 +488,15 @@ function draw_icon!(gui::GUI, design::EnergySystemDesign) θᵢ = 2π * (i - 1) / no_colors θᵢ₊₁ = 2π * i / no_colors end - sector = get_sector_points() + Δh = get_var(gui, :Δh) + xy = design.xy + sector = @lift get_sector_points(; + c = $xy, + Δ = $Δh * get_var(gui, :icon_scale) / 2, + θ₁ = θᵢ, + θ₂ = θᵢ₊₁, + geometry = geometry, + ) network_poly = poly!( get_axes(gui)[:topo], sector; color = color, inspectable = false, @@ -582,43 +506,57 @@ function draw_icon!(gui::GUI, design::EnergySystemDesign) network_poly, (self, i, p) -> get_hover_string(design), ) end - Makie.translate!(network_poly, 0, 0, get_var(gui, :z_translate_components)) - get_vars(gui)[:z_translate_components] += 1 + Makie.translate!( + network_poly, + 0.0f0, + 0.0f0, + get_var(gui, :z_translate_components), + ) + get_vars(gui)[:z_translate_components] += 1.0f0 network_poly.kw[:EMGUI_obj] = design push!(design.plots, network_poly) - on(design.xy; priority = 3) do c - Δ = get_var(gui, :Δh) * get_var(gui, :icon_scale) / 2 - sector = - get_sector_points(; c, Δ, θ₁ = θᵢ, θ₂ = θᵢ₊₁, geometry = geometry) - network_poly[1][] = sector - end end end if isa(node, NetworkNode) - # Add a vertical white separation line to distinguis input resources from output resources - center_box = lines!( + # Add a center box to separate input resources from output resources + Δh = get_var(gui, :Δh) + xy = design.xy + + box = @lift begin + Δ = $Δh * get_var(gui, :icon_scale) / 4 + Rect2f($xy .- Point2f(Δ, Δ)/2, Point2f(Δ, Δ)) + end + + center_box = poly!( get_axes(gui)[:topo], - zeros(4), - zeros(4); - color = :black, - linewidth = get_var(gui, :linewidth), + box; + color = :white, inspectable = false, + strokewidth = get_var(gui, :linewidth), + ) + + add_inspector_to_poly!( + center_box, (self, i, p) -> get_hover_string(design), + ) + + Makie.translate!( + center_box, + 0.0f0, + 0.0f0, + get_var(gui, :z_translate_components), ) - Makie.translate!(center_box, 0, 0, get_var(gui, :z_translate_components)) - get_vars(gui)[:z_translate_components] += 1 + get_vars(gui)[:z_translate_components] += 1.0f0 center_box.kw[:EMGUI_obj] = design push!(design.plots, center_box) - on(design.xy; priority = 3) do center - radius = get_var(gui, :Δh) * get_var(gui, :icon_scale) / 2 - x_coords, y_coords = box(center[1], center[2], radius / 4) - center_box[1][] = Vector{Point{2,Float64}}([ - [x, y] for (x, y) ∈ zip(x_coords, y_coords) - ]) - end end else - @debug "$(design.icon)" + Δh = get_var(gui, :Δh) + xy = design.xy + scale = get_var(gui, :icon_scale) + xo_image = @lift ($xy[1] - $Δh * scale / 2) .. ($xy[1] + $Δh * scale / 2) + yo_image = @lift ($xy[2] - $Δh * scale / 2) .. ($xy[2] + $Δh * scale / 2) + icon_image = image!( get_axes(gui)[:topo], xo_image, @@ -626,8 +564,8 @@ function draw_icon!(gui::GUI, design::EnergySystemDesign) rotr90(FileIO.load(design.icon)); inspectable = false, ) - Makie.translate!(icon_image, 0, 0, get_var(gui, :z_translate_components)) - get_vars(gui)[:z_translate_components] += 1 + Makie.translate!(icon_image, 0.0f0, 0.0f0, get_var(gui, :z_translate_components)) + get_vars(gui)[:z_translate_components] += 1.0f0 icon_image.kw[:EMGUI_obj] = design push!(design.plots, icon_image) end @@ -639,32 +577,29 @@ end Add a label to an `EnergySystemDesign` component. """ function draw_label!(gui::GUI, component::EnergySystemDesign) - xo = Observable(0.0) - yo = Observable(0.0) - alignment = Observable((:left, :top)) - scale = 0.7 + Δh = get_var(gui, :Δh) + xy = component.xy - on(component.xy; priority = 3) do val - x = val[1] - y = val[2] - - if component.wall[] == :E - xo[] = x + get_var(gui, :Δh) * scale - yo[] = y + tuple = @lift begin + shift_xy = if component.wall[] == :E + Point2f($Δh * scale, 0.0f0) elseif component.wall[] == :S - xo[] = x - yo[] = y - get_var(gui, :Δh) * scale + Point2f(0.0f0, -$Δh * scale) elseif component.wall[] == :W - xo[] = x - get_var(gui, :Δh) * scale - yo[] = y + Point2f(-$Δh * scale, 0.0f0) elseif component.wall[] == :N - xo[] = x - yo[] = y + get_var(gui, :Δh) * scale + Point2f(0.0f0, $Δh * scale) end - alignment[] = get_text_alignment(component.wall[]) + xy_label = $xy + shift_xy + alignment = get_text_alignment(component.wall[]) + (xy_label, alignment) end + # Extract observables from the tuple + xy_label_o = @lift $tuple[1] + alignment_o = @lift $tuple[2] + node = get_element(component) if has_invested(component) @@ -674,16 +609,15 @@ function draw_label!(gui::GUI, component::EnergySystemDesign) end label_text = text!( get_axes(gui)[:topo], - xo, - yo; + xy_label_o; text = get_element_label(node), - align = alignment, + align = alignment_o, fontsize = get_var(gui, :fontsize), inspectable = false, color = font_color, ) - Makie.translate!(label_text, 0, 0, get_var(gui, :z_translate_components)) - get_vars(gui)[:z_translate_components] += 1 + Makie.translate!(label_text, 0.0f0, 0.0f0, get_var(gui, :z_translate_components)) + get_vars(gui)[:z_translate_components] += 1.0f0 label_text.kw[:EMGUI_obj] = component push!(get_plots(component), label_text) end @@ -726,7 +660,6 @@ function adjust_limits!(gui::GUI) if Δ_lim_y > Δ_lim_x Δ_lim_x = Δ_lim_y * vars[:ax_aspect_ratio] else - Δ_lim_y < Δ_lim_x Δ_lim_y = Δ_lim_x / vars[:ax_aspect_ratio] end min_x = x_center - Δ_lim_x / 2 diff --git a/src/utils_gen/structures_utils.jl b/src/utils_gen/structures_utils.jl index 626cc2c..9481794 100644 --- a/src/utils_gen/structures_utils.jl +++ b/src/utils_gen/structures_utils.jl @@ -15,15 +15,15 @@ function installed() end """ - place_nodes_in_circle(total_nodes::Int, current_node::Int, r::Real, xₒ::Real, yₒ::Real) + place_nodes_in_circle(total_nodes::Int, current_node::Int, r::Float32, xₒ::Float32, yₒ::Float32) Return coordinate for point number `i` of a total of `n` points evenly distributed around a circle of radius `r` centered at (xₒ, yₒ) from -π/4 to 5π/4. """ -function place_nodes_in_circle(n::Int, i::Int, r::Real, xₒ::Real, yₒ::Real) - θ::Float64 = n == 1 ? π : -π / 4 + 3π / 2 * (1 - (i - 1) / (n - 1)) - x::Float64 = xₒ + r * cos(θ) - y::Float64 = yₒ + r * sin(θ) +function place_nodes_in_circle(n::Int, i::Int, r::Float32, xₒ::Float32, yₒ::Float32) + θ::Float32 = n == 1 ? π : -π / 4 + 3π / 2 * (1 - (i - 1) / (n - 1)) + x::Float32 = xₒ + r * cos(θ) + y::Float32 = yₒ + r * sin(θ) return x, y end diff --git a/src/utils_gen/topo_utils.jl b/src/utils_gen/topo_utils.jl index 68e7718..49a951c 100644 --- a/src/utils_gen/topo_utils.jl +++ b/src/utils_gen/topo_utils.jl @@ -1,16 +1,11 @@ """ - square_intersection( - c::Vector{Tc}, x::Vector{Tx}, - θ::Tθ, Δ::TΔ - ) where {Tc<:Real, Tx<:Real, Tθ<:Real, TΔ<:Real} + square_intersection(c::Point2f, x::Point2f, θ::Float32, Δ::Float32) Calculate the intersection point between a line starting at `x` and direction described by `θ`, and a square with half side lengths `Δ` centered at center `c`. If the line does not intersect the square, the extension of the two facing sides to `x` will be used instead. """ -function square_intersection( - c::Vector{Tc}, x::Vector{Tx}, θ::Tθ, Δ::TΔ, -) where {Tc<:Real,Tx<:Real,Tθ<:Real,TΔ<:Real} +function square_intersection(c::Point2f, x::Point2f, θ::Float32, Δ::Float32) # Direction vector from θ dx = cos(θ) dy = sin(θ) @@ -20,18 +15,24 @@ function square_intersection( ymin, ymax = c[2] - Δ, c[2] + Δ # Parametric line: X = x[1] + t*dx, Y = x[2] + t*dy - ts = Float64[] + ts = Float32[] + ts_out = Float32[] + # Check intersection with vertical sides (x = xmin and x = xmax) if abs(dx) > eps() t1 = (xmin - x[1]) / dx y1 = x[2] + t1 * dy if ymin <= y1 <= ymax push!(ts, t1) + else + push!(ts_out, t1) end t2 = (xmax - x[1]) / dx y2 = x[2] + t2 * dy if ymin <= y2 <= ymax push!(ts, t2) + else + push!(ts_out, t2) end end # Check intersection with horizontal sides (y = ymin and y = ymax) @@ -40,58 +41,31 @@ function square_intersection( x3 = x[1] + t3 * dx if xmin <= x3 <= xmax push!(ts, t3) + else + push!(ts_out, t3) end t4 = (ymax - x[2]) / dy x4 = x[1] + t4 * dx if xmin <= x4 <= xmax push!(ts, t4) - end - end - - # Only consider intersections in the positive direction (t > 0) - ts_pos = filter(t -> t > 0, ts) - if !isempty(ts_pos) - tmin = minimum(ts_pos) - return [x[1] + tmin * dx, x[2] + tmin * dy] - else - # No intersection: find which two sides are "facing" x - # Compute vector from x to center - vx, vy = c[1] - x[1], c[2] - x[2] - # Determine which vertical side is facing x - xside = vx > 0 ? xmin : xmax - # Determine which horizontal side is facing x - yside = vy > 0 ? ymin : ymax - - # Compute intersection with the extension of the facing vertical side - t_v = abs(dx) > eps() ? (xside - x[1]) / dx : Inf - # Compute intersection with the extension of the facing horizontal side - t_h = abs(dy) > eps() ? (yside - x[2]) / dy : Inf - - # Choose the closest positive intersection - t_candidates = filter(t -> t > 0, [t_v, t_h]) - if !isempty(t_candidates) - tmin = minimum(t_candidates) - return [x[1] + tmin * dx, x[2] + tmin * dy] else - @info "No meaningful intersection found." "c = $c" "x = $x" "θ = $θ" "Δ = $Δ" - throw(ArgumentError("No meaningful intersection found.")) + push!(ts_out, t4) end end + + tmin = isempty(ts) ? minimum(abs.(ts_out)) : minimum(abs.(ts)) + + return x + tmin * Point2f(dx, dy) end """ - square_intersection( - c::Tuple{Tc, Tc}, - θ::Tθ, Δ::TΔ - ) where {Tc<:Real, Tx<:Real, Tθ<:Real, TΔ<:Real} + square_intersection(c::Point2f, θ::Float32, Δ::Float32) Calculate the intersection point between a line starting at `c` and direction described by `θ` and a square with half side lengths `Δ` centered at center `c`. """ -function square_intersection( - c::Tuple{Tc,Tc}, θ::Tθ, Δ::TΔ, -) where {Tc<:Real,Tθ<:Real,TΔ<:Real} - return square_intersection(collect(c), collect(c), θ, Δ) +function square_intersection(c::Point2f, θ::Float32, Δ::Float32) + return square_intersection(c, c, θ, Δ) end """ @@ -118,8 +92,8 @@ given the minimum and maximum coordinates `min_x`, `min_y`, `max_x`, and `max_y` function find_min_max_coordinates( design::EnergySystemDesign, min_x::Number, max_x::Number, min_y::Number, max_y::Number, ) - if design.xy !== nothing && !isa(get_parent(design), NothingElement) - x, y = design.xy[] + if !isa(get_parent(design), NothingElement) + x, y = design.xy[][1], design.xy[][2] min_x = min(min_x, x) max_x = max(max_x, x) min_y = min(min_y, y) @@ -155,13 +129,13 @@ function angle(node_1::EnergySystemDesign, node_2::EnergySystemDesign) end """ - angle_difference(angle1, angle2) + angle_difference(angle1::Float32, angle2::Float32) Compute the difference between two angles. """ -function angle_difference(angle1, angle2) - diff = abs(angle1 - angle2) % (2π) - return min(diff, 2π - diff) +function angle_difference(angle1::Float32, angle2::Float32) + diff::Float32 = abs(angle1 - angle2) % Float32(2π) + return min(diff, Float32(2π) - diff) end """ @@ -176,36 +150,36 @@ get_text_alignment(::Val{:S}) = (:center, :top) get_text_alignment(::Val{:N}) = (:center, :bottom) """ - function box(x, y, Δ) + function box(x::Float32, y::Float32, Δ::Float32) Get the coordinates of a box with half side lengths `Δ` and centered at (`x`,`y`) starting at the upper right corner. """ -function box(x::Real, y::Real, Δ::Real) - xs::Vector{Real} = [x + Δ, x - Δ, x - Δ, x + Δ, x + Δ] - ys::Vector{Real} = [y + Δ, y + Δ, y - Δ, y - Δ, y + Δ] +function box(x::Float32, y::Float32, Δ::Float32) + xs::Vector{Float32} = [x + Δ, x - Δ, x - Δ, x + Δ, x + Δ] + ys::Vector{Float32} = [y + Δ, y + Δ, y - Δ, y - Δ, y + Δ] return xs, ys end """ - update_sub_system_locations!(design::EnergySystemDesign, Δ::Tuple{Real,Real}) + update_sub_system_locations!(design::EnergySystemDesign, Δ::Point2f) Update the coordinates of a subsystem of design based on the movement of EnergySystemDesign `design`. """ -function update_sub_system_locations!(design::EnergySystemDesign, Δ::Tuple{Real,Real}) +function update_sub_system_locations!(design::EnergySystemDesign, Δ::Point2f) for component ∈ get_components(design) - get_xy(component)[] = get_xy(component)[] .+ Δ + get_xy(component)[] += Δ end end """ get_sector_points(; - center::Tuple{Real,Real} = (0.0, 0.0), - Δ::Real = 1.0, - θ₁::Real = 0, - θ₂::Real = π/4, + center::Point2f = Point2f(0.0f0, 0.0f0), + Δ::Float32 = 1.0f0, + θ₁::Float32 = 0.0f0, + θ₂::Float32 = Float32(π / 4), steps::Int=200, geometry::Symbol = :circle) @@ -214,20 +188,20 @@ and angles `θ₁` and `θ₂` for a square (geometry = :rect), a circle (geomet triangle (geometry = :triangle). """ function get_sector_points(; - c::Tuple{Real,Real} = (0.0, 0.0), - Δ::Real = 1.0, - θ₁::Real = 0.0, - θ₂::Real = π / 4, + c::Point2f = Point2f(0.0f0, 0.0f0), + Δ::Float32 = 1.0f0, + θ₁::Float32 = 0.0f0, + θ₂::Float32 = Float32(π / 4), steps::Int = 200, geometry::Symbol = :circle, ) if geometry == :circle - θ::Vector{Float64} = LinRange(θ₁, θ₂, Int(round(steps * (θ₂ - θ₁) / (2π)))) - x_coords::Vector{Float64} = Δ * cos.(θ) .+ c[1] - y_coords::Vector{Float64} = Δ * sin.(θ) .+ c[2] + θ::Vector{Float32} = LinRange(θ₁, θ₂, Int(round(steps * (θ₂ - θ₁) / (2π)))) + x_coords::Vector{Float32} = Δ * cos.(θ) .+ c[1] + y_coords::Vector{Float32} = Δ * sin.(θ) .+ c[2] # Include the center and close the polygon - return [c; collect(zip(x_coords, y_coords)); c] + return Point2f[c, collect(zip(x_coords, y_coords))..., c] elseif geometry == :rect if θ₁ == 0 && θ₂ ≈ 2π x_coords, y_coords = box(c[1], c[2], Δ) @@ -235,31 +209,31 @@ function get_sector_points(; else xy1 = square_intersection(c, θ₁, Δ) xy2 = square_intersection(c, θ₂, Δ) - vertices = [c; Tuple(xy1)] + vertices = Point2f[c, xy1] xsign = [1, -1, -1, 1] ysign = [1, 1, -1, -1] - for (i, corner_angle) ∈ enumerate([π / 4, 3π / 4, 5π / 4, 7π / 4]) + for (i, corner_angle) ∈ enumerate(Float32[π/4, 3π/4, 5π/4, 7π/4]) if θ₁ < corner_angle && θ₂ > corner_angle push!(vertices, c .+ (Δ * xsign[i], Δ * ysign[i])) end end - push!(vertices, Tuple(xy2)) + push!(vertices, xy2) push!(vertices, c) return vertices end elseif geometry == :triangle - input::Bool = θ₂ > π / 2 - if input # input resources on a triangle to the left + input::Bool = (θ₁ + θ₂) / 2 > π / 2 + if input # input resources on a triangle to the left f = θ -> -2Δ * θ / π + 2Δ else # output resources on a triangle to the right f = θ -> 2Δ * θ / π end - d::Float64 = Δ / 2 - x::Tuple{Float64,Float64} = input ? c .- (d / 2, 0) : c .+ (d / 2, 0) - x_side::Float64 = input ? -Δ : Δ - xy1 = c .+ (x_side, f(θ₁)) - xy2 = c .+ (x_side, f(θ₂)) - return [x; xy1; xy2; x] + d::Float32 = Δ / 2 + x::Point2f = input ? c .- (d / 2, 0) : c .+ (d / 2, 0) + x_side::Float32 = input ? -Δ : Δ + xy1 = c .+ Point2f(x_side, f(θ₁)) + xy2 = c .+ Point2f(x_side, f(θ₂)) + return Point2f[x, xy1, xy2, x] else @error "Geometry $geometry is not implemented." end diff --git a/src/utils_gen/utils.jl b/src/utils_gen/utils.jl index b7ef8fb..5068fd7 100644 --- a/src/utils_gen/utils.jl +++ b/src/utils_gen/utils.jl @@ -182,24 +182,24 @@ function get_max_installed(::Any, ::Vector{<:TS.TimeStructure}) end """ - mouse_within_axis(ax::Makie.AbstractAxis, mouse_pos::Tuple{Float64,Float64}) + mouse_within_axis(ax::Makie.AbstractAxis, mouse_pos::Tuple{Float32,Float32}) Check if mouse position is within the pixel area of `ax`. """ -function mouse_within_axis(ax::Makie.AbstractAxis, mouse_pos::Tuple{Float64,Float64}) +function mouse_within_axis(ax::Makie.AbstractAxis, mouse_pos::Tuple{Float32,Float32}) origin::Vec2{Int64} = pixelarea(ax.scene)[].origin widths::Vec2{Int64} = pixelarea(ax.scene)[].widths - mouse_pos_loc::Vec2{Float64} = mouse_pos .- origin + mouse_pos_loc::Vec2{Float32} = mouse_pos .- origin - return all(mouse_pos_loc .> 0.0) && all(mouse_pos_loc .- widths .< 0.0) + return all(mouse_pos_loc .> 0.0f0) && all(mouse_pos_loc .- widths .< 0.0f0) end """ - scroll_ylim(ax::Makie.AbstractAxis, val::Float64) + scroll_ylim(ax::Makie.AbstractAxis, val::Float32) Shift the ylim with `val` units to mimic scrolling feature of an axis `ax`. """ -function scroll_ylim(ax::Makie.AbstractAxis, val::Float64) +function scroll_ylim(ax::Makie.AbstractAxis, val::Float32) ylim = collect(ax.yaxis.attributes.limits[]) ylim .+= val if ylim[2] > 1 diff --git a/test/test_interactivity.jl b/test/test_interactivity.jl index 8f78090..ad079f5 100644 --- a/test/test_interactivity.jl +++ b/test/test_interactivity.jl @@ -128,22 +128,22 @@ end @test node2.color[] == :black connection1 = connections[1] # fetch the Area 1 - Area 2 transmission - plt_connection1 = connection1.plots[1][][1] + plt_connection1 = connection1.plots[1][1] pick_component!(gui, plt_connection1; pick_topo_component = true) update!(gui) - @test get_plots(connection1)[1][][1].color[] == get_selection_color(gui) + @test get_plots(connection1)[1][1].color[] == get_selection_color(gui) pick_component!(gui, nothing; pick_topo_component = true) - @test get_plots(connection1)[1][][1].color[] == connection1.colors[1] + @test get_plots(connection1)[1][1].color[] == connection1.colors[1] link1 = get_connections(components[1])[5] # fetch the link to heat pump - plt_link1 = link1.plots[1][][1] + plt_link1 = link1.plots[1][1] pick_component!(gui, plt_link1; pick_topo_component = true) update!(gui) - @test get_plots(link1)[1][][1].color[] == get_selection_color(gui) + @test get_plots(link1)[1][1].color[] == get_selection_color(gui) pick_component!(gui, nothing; pick_topo_component = true) for plot ∈ link1.plots for (i, color) ∈ enumerate(link1.colors) - @test plot[][i].color[] == color + @test plot[i].color[] == color end end end @@ -203,10 +203,10 @@ end # Test reset view button @testset "get_button(gui,:reset_view).clicks" begin - change::Tuple{Real,Real} = (1.3, -5.5) + change::EMGUI.Point2f = EMGUI.Point2f(1.3f0, -5.5f0) xy = components[3].xy - xc::Real = xy[][1] - yc::Real = xy[][2] + xc::Float32 = xy[][1] + yc::Float32 = xy[][2] xy[] = (xc + change[1], yc + change[2]) From a9284ad11c0675de1c77511f655af10e301659dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20Vegard=20Ven=C3=A5s?= Date: Sat, 8 Nov 2025 10:06:38 +0100 Subject: [PATCH 3/9] Add missing tests for show-function on the types AbstractSystem and ProcInvData --- test/test_interactivity.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test_interactivity.jl b/test/test_interactivity.jl index ad079f5..7d1b38b 100644 --- a/test/test_interactivity.jl +++ b/test/test_interactivity.jl @@ -52,6 +52,12 @@ pin_plot_button = get_button(gui, :pin_plot) @test Base.show(design) == dump(design; maxdepth = 1) @test Base.show(component) == dump(component; maxdepth = 1) @test Base.show(connection) == dump(connection; maxdepth = 1) + + inv_data = EMGUI.get_inv_data(design) + @test Base.show(inv_data) == dump(inv_data; maxdepth = 1) + + system = EMGUI.parse_case(case) + @test Base.show(system) == dump(system; maxdepth = 1) end @testset "Test customizing descriptive names" begin From f98a31237c70ccd62391c0fc38c70c831e3d8020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20Vegard=20Ven=C3=A5s?= Date: Sat, 8 Nov 2025 21:35:17 +0100 Subject: [PATCH 4/9] Improve performance of updates to ax_info and other misc improvements --- src/setup_GUI.jl | 23 +++++++------ src/utils_GUI/GUI_utils.jl | 3 +- src/utils_GUI/info_axis_utils.jl | 54 ++++++++++++++++--------------- src/utils_GUI/topo_axis_utils.jl | 7 ++-- src/utils_gen/structures_utils.jl | 4 +-- src/utils_gen/utils.jl | 4 +-- 6 files changed, 51 insertions(+), 44 deletions(-) diff --git a/src/setup_GUI.jl b/src/setup_GUI.jl index 867903d..2406ca9 100644 --- a/src/setup_GUI.jl +++ b/src/setup_GUI.jl @@ -162,10 +162,12 @@ function GUI( "\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", + "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[:info_text] = Observable(vars[:default_text]) + vars[:summary_text] = Observable("No model results") vars[:dragging] = Ref(false) vars[:ctrl_is_pressed] = Ref(false) @@ -410,7 +412,7 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) # Add text at the top left of the axis domain (to print information of the selected/hovered node/connection) text!( ax_info, - vars[:default_text]; + vars[:info_text]; position = (0.01f0, 0.99f0), align = (:left, :top), fontsize = vars[:fontsize], @@ -428,7 +430,7 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) # 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"; + vars[:summary_text]; position = (0.01f0, 0.99f0), align = (:left, :top), fontsize = vars[:fontsize], @@ -467,7 +469,7 @@ 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) + Makie.Label(gridlayout_taskbar[1, 9], " "; tellwidth = false) # Add buttons related to the ax_results object (where the optimization results are plotted) Makie.Label( @@ -539,11 +541,14 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) justification = :right, ) available_data_menu = Makie.Menu( - gridlayout_results_taskbar2[1, 2]; halign = :left, fontsize = vars[:fontsize], + gridlayout_results_taskbar2[1, 2]; + options = zip(["no options"], [nothing]), + halign = :left, + fontsize = vars[:fontsize], ) # Add the following to add flexibility - Makie.Label(gridlayout_results_taskbar3[1, 1], ""; tellwidth = false) + Makie.Label(gridlayout_results_taskbar3[1, 1], " "; tellwidth = false) reset_view_results_button = Makie.Button( gridlayout_results_taskbar3[1, 2]; label = "reset view", @@ -638,7 +643,7 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) ) # Update the title of the figure - vars[:topo_title_obj] = text!( + topo_title_obj = text!( ax, vars[:topo_title_loc_x], vars[:topo_title_loc_y]; @@ -646,7 +651,7 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign) fontsize = vars[:fontsize], ) Makie.translate!( - vars[:topo_title_obj], + topo_title_obj, 0.0f0, 0.0f0, vars[:z_translate_components] + 999.0f0, diff --git a/src/utils_GUI/GUI_utils.jl b/src/utils_GUI/GUI_utils.jl index 53b06a5..679b670 100644 --- a/src/utils_GUI/GUI_utils.jl +++ b/src/utils_GUI/GUI_utils.jl @@ -399,7 +399,8 @@ function initialize_available_data!(gui) investment_overview *= "Investment overview (CAPEX):\n" investment_overview *= inv_overview_components end - get_ax(gui, :summary).scene.plots[1][1][] = investment_overview + summary_text = get_var(gui, :summary_text) + summary_text[] = investment_overview else if !isempty(model) @warn "Total quantities were not computed as model does not contain a feasible solution" diff --git a/src/utils_GUI/info_axis_utils.jl b/src/utils_GUI/info_axis_utils.jl index c8ef329..dfb9063 100644 --- a/src/utils_GUI/info_axis_utils.jl +++ b/src/utils_GUI/info_axis_utils.jl @@ -1,18 +1,17 @@ """ - update_info_box!(gui::GUI, element; indent::Int64=0) + update_info_box!(gui::GUI, element) Based on `element` update the text in info box. """ function update_info_box!(gui::GUI, element) - info_box = get_ax(gui, :info).scene.plots[1][1] + info_text = get_var(gui, :info_text) if isnothing(element) - info_box[] = get_var(gui, :default_text) + info_text[] = get_var(gui, :default_text) return nothing end - info_box[] = "" - print_nested_structure!( + info_text[] = print_nested_structure!( element, - info_box; + ""; vector_limit = 5, show_the_n_last_elements = 1, ) @@ -21,7 +20,7 @@ end """ print_nested_structure!( element, - output; + output::Observable; indent::Int64=0, vector_limit::Int64=typemax(Int64), ) @@ -31,16 +30,16 @@ parameter `vector_limit` is used to truncate large vectors. """ function print_nested_structure!( element, - output; + output::String; indent::Int64 = 0, vector_limit::Int64 = typemax(Int64), show_the_n_last_elements::Int64 = 3, ) if indent == 0 if isa(element, Dict) || isa(element, Vector) - output[] *= "$(typeof(element))\n" + output *= "$(typeof(element))\n" else - output[] *= "$element ($(typeof(element)))\n" + output *= "$element ($(typeof(element)))\n" end end indent += 1 @@ -62,59 +61,62 @@ function print_nested_structure!( if eltype(element) <: expandable for (i, field1) ∈ enumerate(element) if i == vector_limit + 1 - output[] *= indent_str * "...\n" + output *= indent_str * "...\n" continue end if i <= vector_limit || i > length(element) - show_the_n_last_elements if isa(field1, expandable) - output[] *= indent_str * "$i ($(typeof(field1))):\n" - print_nested_structure!(field1, output; indent, vector_limit) + output *= indent_str * "$i ($(typeof(field1))):\n" + output = + print_nested_structure!(field1, output; indent, vector_limit) else - output[] *= indent_str * "$i: $(typeof(field1))($field1)\n" + output *= indent_str * "$i: $(typeof(field1))($field1)\n" end end end else - output[] *= indent_str * "[" + output *= indent_str * "[" for (i, field1) ∈ enumerate(element) if i == vector_limit + 1 - output[] *= " ... " + output *= " ... " continue end if i <= vector_limit || i > length(element) - show_the_n_last_elements - output[] *= "$field1" + output *= "$field1" if i != length(element) - output[] *= ", " + output *= ", " end end end - output[] *= "]\n" + output *= "]\n" end elseif isa(element, Dict) for field1 ∈ keys(element) if isa(element[field1], expandable) - output[] *= indent_str * "$field1 ($(typeof(element[field1]))):\n" - print_nested_structure!(element[field1], output; indent, vector_limit) + output *= indent_str * "$field1 ($(typeof(element[field1]))):\n" + output = + print_nested_structure!(element[field1], output; indent, vector_limit) else - output[] *= indent_str * "$field1 => $(element[field1])\n" + output *= indent_str * "$field1 => $(element[field1])\n" end end else for field1 ∈ fieldnames(typeof(element)) value1 = getfield(element, field1) if isa(value1, expandable) - output[] *= indent_str * "$(field1) ($(typeof(value1))):\n" - print_nested_structure!(value1, output; indent, vector_limit) + output *= indent_str * "$(field1) ($(typeof(value1))):\n" + output = print_nested_structure!(value1, output; indent, vector_limit) else if isa(value1, OperationalProfile) && !isa(value1, FixedProfile) && length(value1.vals) > vector_limit # Truncate large vectors - output[] *= indent_str * "$(field1): $(typeof(value1))\n" + output *= indent_str * "$(field1): $(typeof(value1))\n" else - output[] *= indent_str * "$(field1): $value1\n" + output *= indent_str * "$(field1): $value1\n" end end end end + return output end diff --git a/src/utils_GUI/topo_axis_utils.jl b/src/utils_GUI/topo_axis_utils.jl index cad9af0..7f9d00d 100644 --- a/src/utils_GUI/topo_axis_utils.jl +++ b/src/utils_GUI/topo_axis_utils.jl @@ -686,10 +686,9 @@ end Update the title of `get_axes(gui)[:topo]` based on `get_design(gui)`. """ function update_title!(gui::GUI) - design = get_design(gui) - system = get_system(design) - parent = get_parent(system) - get_var(gui, :title)[] = if isa(parent, NothingElement) + parent = get_parent(gui) + title_obs = get_var(gui, :title) + title_obs[] = if isa(parent, NothingElement) "top_level" else "top_level.$(parent)" diff --git a/src/utils_gen/structures_utils.jl b/src/utils_gen/structures_utils.jl index 9481794..1dc6a84 100644 --- a/src/utils_gen/structures_utils.jl +++ b/src/utils_gen/structures_utils.jl @@ -15,12 +15,12 @@ function installed() end """ - place_nodes_in_circle(total_nodes::Int, current_node::Int, r::Float32, xₒ::Float32, yₒ::Float32) + place_nodes_in_circle(total_nodes::Int64, current_node::Int64, r::Float32, xₒ::Float32, yₒ::Float32) Return coordinate for point number `i` of a total of `n` points evenly distributed around a circle of radius `r` centered at (xₒ, yₒ) from -π/4 to 5π/4. """ -function place_nodes_in_circle(n::Int, i::Int, r::Float32, xₒ::Float32, yₒ::Float32) +function place_nodes_in_circle(n::Int64, i::Int64, r::Float32, xₒ::Float32, yₒ::Float32) θ::Float32 = n == 1 ? π : -π / 4 + 3π / 2 * (1 - (i - 1) / (n - 1)) x::Float32 = xₒ + r * cos(θ) y::Float32 = yₒ + r * sin(θ) diff --git a/src/utils_gen/utils.jl b/src/utils_gen/utils.jl index 5068fd7..80557b3 100644 --- a/src/utils_gen/utils.jl +++ b/src/utils_gen/utils.jl @@ -195,11 +195,11 @@ function mouse_within_axis(ax::Makie.AbstractAxis, mouse_pos::Tuple{Float32,Floa end """ - scroll_ylim(ax::Makie.AbstractAxis, val::Float32) + 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::Float32) +function scroll_ylim(ax::Makie.AbstractAxis, val::Float64) ylim = collect(ax.yaxis.attributes.limits[]) ylim .+= val if ylim[2] > 1 From 904e8c2c118252dfde163b2c53097f89a8f2e7c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20Vegard=20Ven=C3=A5s?= Date: Sun, 9 Nov 2025 14:09:09 +0100 Subject: [PATCH 5/9] Fix bug that made nodes/areas disappear when plotting too many objects (max z_level is 10000) --- src/setup_GUI.jl | 4 ++-- src/utils_GUI/topo_axis_utils.jl | 13 +++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/setup_GUI.jl b/src/setup_GUI.jl index 2406ca9..0c85854 100644 --- a/src/setup_GUI.jl +++ b/src/setup_GUI.jl @@ -142,8 +142,8 @@ function GUI( # Create iterables for plotting objects in layers (z-direction) such that nodes are # neatly placed on top of each other and lines are beneath nodes - vars[:z_translate_lines] = 1000.0f0 - vars[:z_translate_components] = 5000.0f0 + vars[:z_translate_lines] = 10.0f0 + vars[:z_translate_components] = 50.0f0 vars[:selected_systems] = [] diff --git a/src/utils_GUI/topo_axis_utils.jl b/src/utils_GUI/topo_axis_utils.jl index 7f9d00d..b45b578 100644 --- a/src/utils_GUI/topo_axis_utils.jl +++ b/src/utils_GUI/topo_axis_utils.jl @@ -323,9 +323,7 @@ function connect!(gui::GUI, connection::Connection, two_way::Bool) inspectable = true, ) Makie.translate!(sctr, 0, 0, get_var(gui, :z_translate_lines)) - get_vars(gui)[:z_translate_lines] += 1 Makie.translate!(lns, 0, 0, get_var(gui, :z_translate_lines)) - get_vars(gui)[:z_translate_lines] += 1 sctr.kw[:EMGUI_obj] = connection lns.kw[:EMGUI_obj] = connection @@ -333,6 +331,7 @@ function connect!(gui::GUI, connection::Connection, two_way::Bool) push!(arrow_heads, sctr) push!(line_connections, lns) end + get_vars(gui)[:z_translate_lines] += 0.0001f0 end """ @@ -425,7 +424,7 @@ function draw_box!(gui::GUI, design::EnergySystemDesign) ) # Create a white background rectangle to hide lines from connections Makie.translate!(white_rect2, 0.0f0, 0.0f0, get_var(gui, :z_translate_components)) - get_vars(gui)[:z_translate_components] += 1.0f0 + get_vars(gui)[:z_translate_components] += 0.0001f0 push!(design.plots, white_rect2) white_rect2.kw[:EMGUI_obj] = design end @@ -445,7 +444,7 @@ function draw_box!(gui::GUI, design::EnergySystemDesign) add_inspector_to_poly!(white_rect, (self, i, p) -> get_hover_string(design)) Makie.translate!(white_rect, 0.0f0, 0.0f0, get_var(gui, :z_translate_components)) - get_vars(gui)[:z_translate_components] += 1.0f0 + get_vars(gui)[:z_translate_components] += 0.0001f0 push!(design.plots, white_rect) white_rect.kw[:EMGUI_obj] = design @@ -512,7 +511,6 @@ function draw_icon!(gui::GUI, design::EnergySystemDesign) 0.0f0, get_var(gui, :z_translate_components), ) - get_vars(gui)[:z_translate_components] += 1.0f0 network_poly.kw[:EMGUI_obj] = design push!(design.plots, network_poly) end @@ -546,7 +544,6 @@ function draw_icon!(gui::GUI, design::EnergySystemDesign) 0.0f0, get_var(gui, :z_translate_components), ) - get_vars(gui)[:z_translate_components] += 1.0f0 center_box.kw[:EMGUI_obj] = design push!(design.plots, center_box) end @@ -565,10 +562,10 @@ function draw_icon!(gui::GUI, design::EnergySystemDesign) inspectable = false, ) Makie.translate!(icon_image, 0.0f0, 0.0f0, get_var(gui, :z_translate_components)) - get_vars(gui)[:z_translate_components] += 1.0f0 icon_image.kw[:EMGUI_obj] = design push!(design.plots, icon_image) end + get_vars(gui)[:z_translate_components] += 0.0001f0 end """ @@ -617,7 +614,7 @@ function draw_label!(gui::GUI, component::EnergySystemDesign) color = font_color, ) Makie.translate!(label_text, 0.0f0, 0.0f0, get_var(gui, :z_translate_components)) - get_vars(gui)[:z_translate_components] += 1.0f0 + get_vars(gui)[:z_translate_components] += 0.0001f0 label_text.kw[:EMGUI_obj] = component push!(get_plots(component), label_text) end From 1a6c30be9ce8a5211a1ce49a83acc5945b8a0399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20Vegard=20Ven=C3=A5s?= Date: Sun, 9 Nov 2025 15:02:26 +0100 Subject: [PATCH 6/9] Bump version and add updates to NEWS.md --- NEWS.md | 14 ++++++++++++++ Project.toml | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index e7b6b9c..3ec2c07 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,19 @@ # Release notes +## Version 0.5.17 (2025-11-09) + +### Bugfix + +* Fix bug that made nodes/areas disappear when plotting too many objects (max z_level is 10000). +* Fix square_intersection function. + +### Enhancements + +* Use `Float32` instead of `Number`/`Real`/`Float64` for coordinate related computations in topo (also `Point2f` instead of `Tuple` and `Vector`). +* Remove redundant `notify_component` function and `Observable`s (use the `@lift` macro instead). +* Improve performance of updates to `ax_info`. +* Add missing tests for show-function on the types `AbstractSystem` and `ProcInvData`, and improve code structure. + ## Version 0.5.16 (2025-09-24) ### Enhancement diff --git a/Project.toml b/Project.toml index 1ae8bda..7549eb5 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.16" +version = "0.5.17" [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" From 3bdefa5edccb7d942eef5618689251d13ec2ccc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20Vegard=20Ven=C3=A5s?= Date: Sun, 9 Nov 2025 18:20:18 +0100 Subject: [PATCH 7/9] Run tests on Julia version 1.11 due to incompatibility with version 1.12 --- .github/workflows/ci.yml | 4 ++-- README.md | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1094539..8cfe6a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,10 +16,10 @@ jobs: # Since EnergyModelsGUI doesn't have binary dependencies, # only test on a subset of possible platforms. include: - - version: '1' # The latest point-release (Linux) + - version: '1.11' # The latest point-release (Linux) os: ubuntu-latest arch: x64 - #- version: '1' # The latest point-release (Windows) + #- version: '1.11' # The latest point-release (Windows) # os: windows-latest # arch: x64 - version: 'lts' # lts diff --git a/README.md b/README.md index f846f17..c0b6474 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ Visualization of the results after simulations will be added at a later stage. The EnergyModelsGUI package has taken inspiration from the source code of [ModelingToolkitDesigner](https://github.com/bradcarman/ModelingToolkitDesigner.jl) as a starting point for development. +> [!WARNING] +> EnergyModelsGUI.jl currently does not support Julia verison 1.12 due to a breaking change in GLMakie. It is assumed that this issue will resolve soon in the future. + ## Usage If you already have constructed a `case` in EMX you can view this case with From 68d6efcdb374661fb091693466944777f53f1575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20Vegard=20Ven=C3=A5s?= Date: Sun, 9 Nov 2025 18:34:27 +0100 Subject: [PATCH 8/9] Also run documentation on Julia version 1.11 --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 80be4f4..6e363bd 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@latest with: - version: '1' + version: '1.11' - run: sudo apt-get update && sudo apt-get install -y xorg-dev mesa-utils xvfb libgl1 freeglut3-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxext-dev xsettingsd x11-xserver-utils - name: Install dependencies shell: julia --color=yes --project=docs/ {0} From c25a50228f86bc7caf8612904139223f35696ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20Vegard=20Ven=C3=A5s?= Date: Sun, 9 Nov 2025 20:54:43 +0100 Subject: [PATCH 9/9] Restrict IntervalSets package to resolve circular dependency --- Project.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Project.toml b/Project.toml index 7549eb5..4971895 100644 --- a/Project.toml +++ b/Project.toml @@ -17,6 +17,7 @@ GeoJSON = "61d90e0f-e114-555e-ac52-39dfb47a3ef9" GeoMakie = "db073c08-6b98-4ee5-b6a4-5efafb3259c6" HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" +IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" @@ -47,6 +48,7 @@ GeoJSON = "0.8" GeoMakie = "=0.7.12" HTTP = "1.10" ImageMagick = "1.3" +IntervalSets = "<0.7.12" JuMP = "1.22" Pkg = "1.9" PrettyTables = "2.3"