diff --git a/src/Config/ForceDirectedGraph.elm b/src/Config/ForceDirectedGraph.elm index 7d2292c47952436483fcedd406e358fded0252c8..59cb1e0018656c4253463f4aaf813ba1bdd8e875 100644 --- a/src/Config/ForceDirectedGraph.elm +++ b/src/Config/ForceDirectedGraph.elm @@ -9,9 +9,8 @@ type Msg = RecalcStart | Highlight String | ChangeYear Int - | ChangeForces Int + | ChangeVersion Int | RecalcEnd (Result TaskError Model) - | SetLoading Bool type TaskError @@ -25,6 +24,7 @@ type alias Model = , loading : Bool , highlight : String , year : Int + , version : Int } diff --git a/src/Main.elm b/src/Main.elm index 0156a7299c5eacd18072f9cd184d5bf3f28c318a..b49e3b8ebb118ee19fd21a227cb0a3962b7263c3 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -88,7 +88,9 @@ update msg ( global, local ) = ViewChange newView -> case newView of ForceDirectedGraph -> - ( ( { global | view = newView }, local ), ForceDirectedGraphView.calculateModel global.data 0 |> Cmd.map ForceDirectedGraphView.propagate ) + ( ( { global | view = newView }, local ) + , ForceDirectedGraphView.calculateModel global.data local.forcedirectedgraph |> Cmd.map ForceDirectedGraphView.propagate + ) _ -> ( ( { global | view = newView }, local ), Cmd.none ) diff --git a/src/Views/ForceDirectedGraph.elm b/src/Views/ForceDirectedGraph.elm index 0fbeffd9d5482d321b6216c91570619e8cd4ca76..5b227416599547ecc7064e58520d14329ed2d106 100644 --- a/src/Views/ForceDirectedGraph.elm +++ b/src/Views/ForceDirectedGraph.elm @@ -44,15 +44,17 @@ view global local = [ Html.div [] [ Html.h2 [ style "margin-top" "0" ] [ Html.text "Force directed Graph" ] - , Html.button [ Local.ChangeForces 0 |> propagate |> onClick ] - [ Html.i [ Html.Attributes.class "fa-solid fa-repeat fa-xl" ] [] ] - , Html.button [ Local.ChangeForces 1 |> propagate |> onClick ] - [ Html.i [ Html.Attributes.class "fa-solid fa-repeat fa-xl" ] [] ] - , Html.button [ Local.ChangeForces 2 |> propagate |> onClick ] - [ Html.i [ Html.Attributes.class "fa-solid fa-repeat fa-xl" ] [] ] + , Html.div [ style "display" "flex", style "flex-direction" "row", style "gap" "0.5rem" ] + [ Html.button [ Local.ChangeVersion 0 |> propagate |> onClick ] + [ Html.text "Version 1" ] + , Html.button [ Local.ChangeVersion 1 |> propagate |> onClick ] + [ Html.text "Version 2" ] + , Html.button [ Local.ChangeVersion 2 |> propagate |> onClick ] + [ Html.text "Version 3" ] + , Html.button [ Local.ChangeVersion 3 |> propagate |> onClick ] + [ Html.text "Version 4" ] + ] ] - , Html.button [ Local.RecalcStart |> propagate |> onClick, style "margin-left" "auto" ] - [ Html.i [ Html.Attributes.class "fa-solid fa-repeat fa-xl" ] [] ] , Html.div [ style "margin-left" "auto" ] [ Html.text "Jahr: " , allYears @@ -82,7 +84,7 @@ view global local = [ style "position" "relative" , style "bottom" "5rem" ] - [ Html.text "Loading..." ] + [ Html.text "Simulating..." ] ] else @@ -161,7 +163,7 @@ initializeNode ctx = firstInit : Local.Model firstInit = - Local.Model Graph.empty (Force.simulation []) 0 True "" 0 + Local.Model Graph.empty (Force.simulation []) 0 True "" 0 0 makeGraph : List Entry -> Int -> Graph String () @@ -196,12 +198,18 @@ makeGraph data year = Graph.fromNodeLabelsAndEdgePairs allCountries edges -postInit : List Entry -> Int -> Task.Task Never (Result Local.TaskError Local.Model) -postInit data year = +postInit : List Entry -> Local.Model -> Task.Task Never (Result Local.TaskError Local.Model) +postInit data local = let + year = + local.year + filteredData = filterForYears data year + _ = + Debug.log "filteredData" filteredData + countryNames = List.map (\ent -> String.toUpper ent.state) filteredData @@ -221,143 +229,184 @@ postInit data year = rowsWithBoth : String -> String -> List Entry rowsWithBoth s o = - List.filter (\ent -> String.toUpper ent.state == String.toUpper s && String.toUpper ent.consulateCountry == String.toUpper o) filteredData + List.filter + (\ent -> + String.toUpper ent.state == String.toUpper s && String.toUpper ent.consulateCountry == String.toUpper o + ) + filteredData graph = Graph.mapContexts initializeNode <| makeGraph filteredData year forces = - [ Graph.nodes graph - |> List.map - (\n -> - let - cc = - List.Extra.getAt (findName n.id |> findPos) allCountries - |> Maybe.withDefault "??" - - rows = - List.filter (\e -> e.state == cc) filteredData - - acceptedRatio = - (List.map (\e -> Maybe.withDefault 0 <| e.totalVisasApplied) rows - |> List.Statistics.mean - |> Maybe.withDefault 1 + (case local.version of + 0 -> + [ Graph.nodes graph + |> List.map + (\i -> + if isSchengen i filteredData then + ( i.id, { strength = 4.0, x = w / 2, y = h / 2, radius = min h w / 3.0 } ) + + else + ( i.id, { strength = 4.0, x = w / 2, y = h / 2, radius = min h w / 1.2 } ) + ) + |> Force.customRadial + , Graph.nodes graph + |> List.map + (\e -> + ( e.id + , if List.member (findName e.id) countryNames then + 30 + + else + 10 ) - / (List.map (\e -> Maybe.withDefault 0 <| e.totalVisasIssued) rows - |> List.Statistics.mean - |> Maybe.withDefault 1 - ) - in - ( n.id, 0.5 * acceptedRatio ) - ) - |> Force.customManyBody 0.99 - - --Force.customLinks 1 - -- (List.map link (Graph.edges graph)) - -- Force.manyBodyStrength -10 <| - -- List.map .id <| - -- Graph.nodes graph - , Graph.nodes graph - |> List.map - (\e -> - ( e.id - , if List.member (findName e.id) countryNames then - 30 - - else - 10 - ) - ) - |> Force.customCollision { iterations = 1, strength = 1.0 } - - --Graph.edges graph - -- |> List.map - -- (\e -> - -- let - -- meanNotIssRat = - -- rowsWithBoth (findName e.from) (findName e.to) - -- |> List.map - -- (\ent -> - -- let - -- totApp = - -- Maybe.withDefault 0 <| ent.totalVisasApplied - -- totIss = - -- Maybe.withDefault 0 <| ent.totalVisasIssued - -- notIss = - -- totApp - totIss - -- notIssRat = - -- notIss / totApp - -- in - -- if not (isNaN notIssRat) && not (notIssRat < 0) then - -- notIssRat - -- else - -- 0 - -- ) - -- |> List.Statistics.mean - -- |> Maybe.withDefault 0 - -- in - -- { source = e.from, target = e.to, distance = min w h * (1 - meanNotIssRat) / 1.5, strength = Just 1 } - -- ) - -- |> Force.customLinks 3 - --, Graph.edges graph - -- |> List.map - -- (\i -> - -- if - -- isSchengen - -- (case Graph.get i.from graph of - -- Just ctx -> - -- ctx.node _ -> Graph.Node 0 (Force.entity 0 "") - -- ) - -- data - -- && isSchengen - -- (case Graph.get i.to graph of - -- Just ctx -> - -- ctx.node - -- _ -> - -- Graph.Node 0 (Force.entity 0 "") - -- ) - -- data - -- || not - -- (isSchengen - -- (case Graph.get i.from graph of - -- Just ctx -> - -- ctx.node - -- _ -> - -- Graph.Node 0 (Force.entity 0 "") - -- ) - -- data - -- ) - -- && not - -- (isSchengen - -- (case Graph.get i.to graph of - -- Just ctx -> - -- ctx.node - -- _ -> - -- Graph.Node 0 (Force.entity 0 "") - -- ) - -- data - -- ) - -- then - -- { source = i.from, target = i.to, distance = 3, strength = Just 2 } - -- else - -- { source = i.from, target = i.to, distance = 10, strength = Just 1 } - -- ) - -- |> Force.customLinks 1 - , Graph.nodes graph - |> List.map - (\i -> - if isSchengen i data then - ( i.id, { strength = 4.0, x = w / 2, y = h / 2, radius = min h w / 3.0 } ) - - else - ( i.id, { strength = 4.0, x = w / 2, y = h / 2, radius = min h w / 1.2 } ) - ) - |> Force.customRadial - , Force.center - (w / 2) - (h / 2) - ] + ) + |> Force.customCollision { iterations = 1, strength = 1.0 } + ] + 1 -> + [ Force.manyBodyStrength -8 <| + List.map .id <| + Graph.nodes graph + ] + + 2 -> + [ Graph.nodes graph + |> List.map + (\n -> + let + cc = + List.Extra.getAt (findName n.id |> findPos) allCountries + |> Maybe.withDefault "??" + + rows = + List.filter (\e -> e.state == cc) filteredData + + acceptedRatio = + (List.map (\e -> Maybe.withDefault 0 <| e.totalVisasIssued) rows + |> List.Statistics.mean + |> Maybe.withDefault 1 + ) + / (List.map (\e -> Maybe.withDefault 0 <| e.totalVisasApplied) rows + |> List.Statistics.mean + |> Maybe.withDefault 1 + ) + in + ( n.id, -50 * (1.1 - acceptedRatio) ) + ) + |> Force.customManyBody 0.9 + , Graph.nodes graph + |> List.map + (\e -> + ( e.id + , if List.member (findName e.id) countryNames then + 30 + + else + 10 + ) + ) + |> Force.customCollision { iterations = 1, strength = 1.0 } + ] + + 3 -> + [ Graph.edges graph + |> List.map + (\e -> + let + meanIssRat = + rowsWithBoth (findName e.from) (findName e.to) + |> List.map + (\ent -> + let + totApp = + Maybe.withDefault 1 <| ent.totalVisasApplied + + totIss = + Maybe.withDefault 0 <| ent.totalVisasIssued + + issRat = + totIss / totApp + in + if isNaN issRat then + 0 + + else if isInfinite issRat then + 1 + + else + issRat + ) + |> List.Statistics.mean + |> Maybe.withDefault 0 + + _ = + Debug.log "meanIssRat" meanIssRat + in + { source = e.from, target = e.to, distance = (min w h / 1.5) * meanIssRat, strength = Just 1 } + ) + |> Force.customLinks 3 + , Force.manyBodyStrength -30 <| + List.map .id <| + Graph.nodes graph + , Force.collision 10 <| List.map .id <| Graph.nodes graph + ] + + _ -> + [] + ) + ++ [ Force.center + (w / 2) + (h / 2) + ] + + --Force.customLinks 1 + -- (List.map link (Graph.edges graph)) + --, Graph.edges graph + -- |> List.map + -- (\i -> + -- if + -- isSchengen + -- (case Graph.get i.from graph of + -- Just ctx -> + -- ctx.node _ -> Graph.Node 0 (Force.entity 0 "") + -- ) + -- data + -- && isSchengen + -- (case Graph.get i.to graph of + -- Just ctx -> + -- ctx.node + -- _ -> + -- Graph.Node 0 (Force.entity 0 "") + -- ) + -- data + -- || not + -- (isSchengen + -- (case Graph.get i.from graph of + -- Just ctx -> + -- ctx.node + -- _ -> + -- Graph.Node 0 (Force.entity 0 "") + -- ) + -- data + -- ) + -- && not + -- (isSchengen + -- (case Graph.get i.to graph of + -- Just ctx -> + -- ctx.node + -- _ -> + -- Graph.Node 0 (Force.entity 0 "") + -- ) + -- data + -- ) + -- then + -- { source = i.from, target = i.to, distance = 3, strength = Just 2 } + -- else + -- { source = i.from, target = i.to, distance = 10, strength = Just 1 } + -- ) + -- |> Force.customLinks 1 list = Force.computeSimulation (Force.simulation forces) <| List.map .label <| Graph.nodes graph in @@ -366,7 +415,7 @@ postInit data year = (\_ -> Task.succeed (Ok - (Local.Model (updateGraphWithList graph list) (Force.simulation forces) 0 False "" 0) + (Local.Model (updateGraphWithList graph list) (Force.simulation forces) 0 False "" 0 0) ) ) @@ -413,28 +462,25 @@ updateGraphWithList = List.foldr (\node graph -> Graph.update node.id (graphUpdater node) graph) -calculateModel : List Entry -> Int -> Cmd Local.Msg -calculateModel data year = - Task.perform Local.RecalcEnd (Process.sleep 0 |> Task.andThen (\_ -> postInit data year)) +calculateModel : List Entry -> Local.Model -> Cmd Local.Msg +calculateModel data local = + Task.perform Local.RecalcEnd (Process.sleep 0 |> Task.andThen (\_ -> postInit data local)) update : Local.Msg -> Global -> Local.Model -> ( Global, Local.Model, Cmd Msg ) -update msg glob { graph, simulation, tickCount, loading, highlight, year } = +update msg glob loc = case msg of Local.RecalcStart -> - ( glob, Local.Model graph simulation tickCount True highlight year, calculateModel glob.data year |> Cmd.map propagate ) + ( glob, { loc | loading = True }, calculateModel glob.data loc |> Cmd.map propagate ) Local.Highlight hl -> - ( glob, Local.Model graph simulation tickCount loading hl year, Cmd.none ) + ( glob, { loc | highlight = hl }, Cmd.none ) Local.ChangeYear y -> - ( glob, Local.Model graph simulation tickCount True highlight year, calculateModel glob.data y |> Cmd.map propagate ) - - Local.ChangeForces f -> - ( glob, Local.Model graph simulation tickCount loading highlight year, Cmd.none ) + ( glob, { loc | loading = True }, calculateModel glob.data { loc | year = y } |> Cmd.map propagate ) - Local.SetLoading b -> - ( glob, Local.Model graph simulation tickCount b highlight year, Cmd.none ) + Local.ChangeVersion v -> + ( glob, { loc | loading = True }, calculateModel glob.data { loc | version = v } |> Cmd.map propagate ) Local.RecalcEnd res -> case res of @@ -442,7 +488,7 @@ update msg glob { graph, simulation, tickCount, loading, highlight, year } = ( glob, { newModel | loading = False }, Cmd.none ) Err _ -> - ( glob, Local.Model graph simulation tickCount False highlight year, Cmd.none ) + ( glob, { loc | loading = False }, Cmd.none ) propagate : Local.Msg -> Msg