diff --git a/src/Config/BarPlotConfig.elm b/src/Config/BarPlotConfig.elm index d6f4b702983f16ea3fe637c8be95d3fd1b3fbceb..dfdb9fc68a2eefd268e089900690b756dcac860e 100644 --- a/src/Config/BarPlotConfig.elm +++ b/src/Config/BarPlotConfig.elm @@ -1,8 +1,9 @@ module Config.BarPlotConfig exposing (..) type alias Model = - { ubound: Maybe Int, multiplier: Int } + { ubound: Maybe Int, multiplier: Int, visatype: String } type Msg = UboundChange (Maybe Int) - | MultiplierChange Int \ No newline at end of file + | MultiplierChange Int + | VisatypeChange String \ No newline at end of file diff --git a/src/Config/LinePlotConfig.elm b/src/Config/LinePlotConfig.elm index 613df3c56906a6c5b9d10a1da6ea362301ba3d36..d94aa6421fc980eb78c6cbd3a49bca215e2e50a8 100644 --- a/src/Config/LinePlotConfig.elm +++ b/src/Config/LinePlotConfig.elm @@ -9,4 +9,5 @@ type alias Model = type Msg = CountryChange String - | Hint (Maybe Line.Hint) \ No newline at end of file + | Hint (Maybe Line.Hint) + | ConsulateChange String \ No newline at end of file diff --git a/src/Main.elm b/src/Main.elm index 3a575182f657811160032ce507a7355a43f38069..0156a7299c5eacd18072f9cd184d5bf3f28c318a 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -44,6 +44,7 @@ init () = , progress = Loading , data = [] , selectedCountry = Nothing + , selectedConsulateCountry = Nothing } local : Local diff --git a/src/Model.elm b/src/Model.elm index 13dbd56b033081aa0508c89ad285cfa68f69a132..923afd8c75e3b63c83a6d0a193097923db1d1a26 100644 --- a/src/Model.elm +++ b/src/Model.elm @@ -31,6 +31,7 @@ type alias Global = -- extend as needed... , selectedCountry : Maybe String + , selectedConsulateCountry : Maybe String } diff --git a/src/Views/BarPlot.elm b/src/Views/BarPlot.elm index 41ae70ce4de22f04392a4b92e072d4cc7b9443be..427098990d8c139db6f464f94a2a3d270890dde2 100644 --- a/src/Views/BarPlot.elm +++ b/src/Views/BarPlot.elm @@ -2,7 +2,7 @@ module Views.BarPlot exposing (view, init, update) import Config.BarPlotConfig as Local exposing (Model) -import Html exposing (Html, div, input, label) +import Html exposing (Html, div, input, label, select, option) import Html.Attributes exposing (type_, style) import Html.Events exposing (onClick, onInput) @@ -37,7 +37,7 @@ padding = init : Model -init = { ubound = Nothing, multiplier = 1 } +init = { ubound = Nothing, multiplier = 1, visatype = "totalVisasApplied" } propagate : Local.Msg -> Msg propagate msg = @@ -50,17 +50,19 @@ update msg global model = ( global, { model | ubound = ubound }, Cmd.none ) Local.MultiplierChange multiplier -> ( global, { model | multiplier = multiplier }, Cmd.none ) + Local.VisatypeChange visatype -> + ( global, { model | visatype = visatype }, Cmd.none ) view : Global -> Model -> Html Msg view global model = let - -- Iterate global.data and sum totalVisasApplied for each state + -- Iterate global.data and sum totalVisasApplied/totalVisasIssued for each state totalList : List (String, Float) totalList = global.data - |> List.map (\entry -> (entry.state, Maybe.withDefault 0 entry.totalVisasApplied)) + |> List.map (\entry -> (entry.state, Maybe.withDefault 0 (if model.visatype == "totalVisasApplied" then entry.totalVisasApplied else entry.totalVisasIssued))) |> List.foldl (\(state, count) acc -> Dict.update state @@ -73,7 +75,7 @@ view global model = |> Dict.toList |> List.map (\(state, count) -> (state, count)) - -- Filter list to only include states with totalVisasApplied >= given ubound + -- Filter list to only include states with totalVisasApplied/totalVisasIssued >= given ubound filteredList : List (String, Float) filteredList = let @@ -160,14 +162,22 @@ view global model = |> Local.MultiplierChange |> propagate + -- Handle visatype change + handleVisatypeChange : String -> Msg + handleVisatypeChange msg = + Local.VisatypeChange msg + |> propagate + -- View function to render the chart viewPlot : List ( String, Float ) -> Svg msg viewPlot stateData = svg [ viewBox 0 0 w h ] [ TypedSvg.style [] [ Html.text """ - .column rect { fill: rgba(2, 48, 32, 0.7); } + -- .column rect { fill: rgba(2, 48, 32, 0.7); } + .column rect { fill: rgb(78, 154, 6); } .column text { display: none; font-size: 14px } - .column:hover rect { fill: rgb(2, 48, 32); } + -- .column:hover rect { fill: rgb(2, 48, 32); } + .column:hover rect { fill: rgb(57, 110, 6); } .column:hover text { display: inline; } .xAxis .tick text { transform: translate(-25px, 20px) rotate(-45deg); """ ] @@ -188,46 +198,66 @@ view global model = , x <| padding , y <| padding - 10 , fontSize 12 ] - [ Html.text "Total Visas Applied" ] + [ Html.text (if model.visatype == "totalVisasApplied" then "Total Visas Applied" else "Total Visas Issued") ] ] in div [ Html.Attributes.class "barplot-wrapper" ] - [ div [] [ - Html.text "Upper bound: " - , input - [ Html.Attributes.type_ "number" - , Html.Attributes.placeholder "Enter..." - , Html.Attributes.min "0" - , Html.Attributes.value (model.ubound |> Maybe.map String.fromInt |> Maybe.withDefault "" ) - , onInput handleUboundChange - ] [] - , input - [ Html.Attributes.id "rad_1" - , Html.Attributes.name "multiplier" - , Html.Attributes.type_ "radio" - , Html.Attributes.value "1" - , Html.Attributes.checked (model.multiplier == 1) - , onInput handleMultiplierChange - ] [] - , label [Html.Attributes.for "rad_k"] [ Html.text "×1" ] - , input - [ Html.Attributes.id "rad_k" - , Html.Attributes.name "multiplier" - , Html.Attributes.type_ "radio" - , Html.Attributes.value "1000" - , Html.Attributes.checked (model.multiplier == 1000) - , onInput handleMultiplierChange - ] [] - , label [Html.Attributes.for "rad_k"] [ Html.text "×1K" ] - , input - [ Html.Attributes.id "rad_m" - , Html.Attributes.name "multiplier" - , Html.Attributes.type_ "radio" - , Html.Attributes.value "1000000" - , Html.Attributes.checked (model.multiplier == 1000000) - , onInput handleMultiplierChange - ] [] - , label [Html.Attributes.for "rad_m"] [ Html.text "×1M" ] + [ div [ Html.Attributes.style "display" "flex", Html.Attributes.style "flex-direction" "column", Html.Attributes.style "gap" "6px" ] [ + + div [ Html.Attributes.style "display" "flex", Html.Attributes.style "flex-direction" "row", Html.Attributes.style "gap" "6px" ] + [ label [Html.Attributes.for "select_visatype"] [ Html.text "Choose Visa Type: " ] + , select + [ Html.Attributes.id "select_visatype" + , Html.Attributes.style "width" "10rem" + , Html.Attributes.name "visatype" + , onInput handleVisatypeChange + ] + [ option + [ Html.Attributes.value "totalVisasApplied" ] + [ Html.text "Total Visas Applied" ] + , option + [ Html.Attributes.value "totalVisasIssued" ] + [ Html.text "Total Visas Issued" ] + ] + ] + + , div[] [ + Html.text "Upper bound: " + , input + [ Html.Attributes.type_ "number" + , Html.Attributes.placeholder "Enter..." + , Html.Attributes.min "0" + , Html.Attributes.value (model.ubound |> Maybe.map String.fromInt |> Maybe.withDefault "" ) + , onInput handleUboundChange + ] [] + , input + [ Html.Attributes.id "rad_1" + , Html.Attributes.name "multiplier" + , Html.Attributes.type_ "radio" + , Html.Attributes.value "1" + , Html.Attributes.checked (model.multiplier == 1) + , onInput handleMultiplierChange + ] [] + , label [Html.Attributes.for "rad_k"] [ Html.text "×1" ] + , input + [ Html.Attributes.id "rad_k" + , Html.Attributes.name "multiplier" + , Html.Attributes.type_ "radio" + , Html.Attributes.value "1000" + , Html.Attributes.checked (model.multiplier == 1000) + , onInput handleMultiplierChange + ] [] + , label [Html.Attributes.for "rad_k"] [ Html.text "×1K" ] + , input + [ Html.Attributes.id "rad_m" + , Html.Attributes.name "multiplier" + , Html.Attributes.type_ "radio" + , Html.Attributes.value "1000000" + , Html.Attributes.checked (model.multiplier == 1000000) + , onInput handleMultiplierChange + ] [] + , label [Html.Attributes.for "rad_m"] [ Html.text "×1M" ] + ] ] , viewPlot filteredList ] diff --git a/src/Views/LinePlot.elm b/src/Views/LinePlot.elm index 9a6566179fb15e0b038b54929c01b3352eb77053..2c431e875ff42939f24b52cc3793e4149b5ea6cb 100644 --- a/src/Views/LinePlot.elm +++ b/src/Views/LinePlot.elm @@ -4,22 +4,18 @@ import Msg exposing (Msg(..)) import Data.DataLoader exposing (Entry) import Html exposing (Html, div, select, option, label) -import Html.Attributes exposing (type_, style) -import Html.Events exposing (onClick, onInput) +import Html.Attributes +import Html.Events exposing (onInput) import List.Extra exposing (unique) -import Dict exposing (Dict) +import Dict import Chart.Line as Line import Scale.Color -import Chart.Symbol as Symbol exposing (Symbol) +import Chart.Symbol as Symbol import Axis -import Chart.Annotation as Annotation -import TypedSvg.Core exposing (Svg) import Config.LinePlotConfig as Local exposing (Model) -import Html.Attributes exposing (wrap) - import Utils exposing (codeToName) init : Model @@ -34,6 +30,8 @@ update msg global model = case msg of Local.CountryChange country -> ( { global | selectedCountry = Just country }, model, Cmd.none ) + Local.ConsulateChange consulateCountry -> + ( { global | selectedConsulateCountry = Just consulateCountry }, model, Cmd.none ) Local.Hint response -> ( global, { model | pointAnnotation = @@ -70,7 +68,7 @@ view global model = List.map .state global.data |> unique - -- Generate a list of options for the select element + -- Generate a list of country options for the select element country_option : String -> Html Msg country_option country = let @@ -84,12 +82,44 @@ view global model = [ Html.Attributes.value country, Html.Attributes.selected selected ] [ Html.text (codeToName country) ] + -- Get a list of all consulate countries from global.data + consulate_countries : List String + consulate_countries = + let + consulates : List (String, String) + consulates = global.data + |> List.filter (\entry -> not (entry.totalIssuedMEV == Nothing || entry.totalVisasApplied == Nothing || entry.totalVisasIssued == Nothing)) + |> List.map (\entry -> (entry.consulateCountry, entry.state)) + |> List.filter (\(_, country) -> country == country_default) + in + List.map Tuple.first consulates + |> unique + |> List.filter (\x -> not (List.member x countries)) -- Filter out countries that are schengen states + |> List.sortBy (\x -> (codeToName x)) + |> List.append [""] -- Add an empty string to show all consulates + + default_consulateCountry : String + default_consulateCountry = + let + cc : String + cc = Maybe.withDefault "" global.selectedConsulateCountry + in + if (List.member (cc) consulate_countries) then cc else "" + + -- Generate a list of consulate country options for the select element + consulate_option : String -> Html Msg + consulate_option country = + option + [ Html.Attributes.value country, Html.Attributes.selected (country == default_consulateCountry) ] + [ Html.text (if not (country == "") then (codeToName country) else "-- Alle --") ] - -- Filter global.data based on the selected visa type and country - filterList : List Entry -> String -> String -> List (Int, Int) - filterList data country visatype = + -- Filter global.data based on the selected visa type, consulate and country + filterList : List Entry -> String -> String -> String -> List (Int, Int) + filterList data country visatype consulate = data |> List.filter (\entry -> entry.state == country) + -- If consulate is not empty, filter by consulate else show all + |> List.filter (\entry -> if consulate == "" then True else entry.consulateCountry == consulate) |> List.map (\entry -> (entry.year, Maybe.withDefault 0 ( case visatype of "totalVisasApplied" -> entry.totalVisasApplied @@ -108,6 +138,7 @@ view global model = Dict.empty |> Dict.toList |> List.map (\(year, count) -> (year, round count)) + -- |> List.filter (\(_, count) -> count > 0) -- Get a list of all years from global.data for the xaxis labeling of the selected country @@ -115,10 +146,10 @@ view global model = getYears = let applied_years : List Int - applied_years = List.map Tuple.first (filterList global.data country_default "totalVisasApplied") + applied_years = List.map Tuple.first (filterList global.data country_default "totalVisasApplied" default_consulateCountry) issued_years : List Int - issued_years = List.map Tuple.first (filterList global.data country_default "totalVisasIssued") + issued_years = List.map Tuple.first (filterList global.data country_default "totalVisasIssued" default_consulateCountry) in List.append applied_years issued_years |> List.sort @@ -132,10 +163,11 @@ view global model = filterVApplied : List DataPoint - filterVApplied = wrapDataPoint (filterList global.data country_default "totalVisasApplied") "applied" + filterVApplied = wrapDataPoint (filterList global.data country_default "totalVisasApplied" default_consulateCountry) "applied" filterVIssued : List DataPoint - filterVIssued = wrapDataPoint (filterList global.data country_default "totalVisasIssued") "issued" + filterVIssued = wrapDataPoint (filterList global.data country_default "totalVisasIssued" default_consulateCountry) "issued" + -- Merge the two lists dataLines : List DataPoint @@ -299,19 +331,44 @@ view global model = |> Local.CountryChange |> propagate + -- Handle consulate change + handleConsulateChange : String -> Msg + handleConsulateChange msg = + msg + |> Local.ConsulateChange + |> propagate + in div [ Html.Attributes.class "barplot-wrapper" ] [ div [ Html.Attributes.style "display" "flex" , Html.Attributes.style "flex-direction" "column" , Html.Attributes.style "gap" "4px" ] [ - label [Html.Attributes.for "select_country"] [ Html.text "Choose Schengen State: " ] - , select - [ Html.Attributes.id "select_country" - , Html.Attributes.name "country" - , onInput handleCountryChange - ] - (List.map country_option countries) + div [ Html.Attributes.style "display" "flex" + , Html.Attributes.style "flex-direction" "column" + , Html.Attributes.style "gap" "6px"] [ + + div [ Html.Attributes.style "display" "flex", Html.Attributes.style "flex-direction" "row", Html.Attributes.style "gap" "6px" ] [ + label [Html.Attributes.for "select_country"] [ Html.text "Choose Schengen State: " ] + , select + [ Html.Attributes.id "select_country" + , Html.Attributes.name "country" + , Html.Attributes.style "width" "10rem" + , onInput handleCountryChange + ] + (List.map country_option countries) + ] + , div [ Html.Attributes.style "display" "flex", Html.Attributes.style "flex-direction" "row", Html.Attributes.style "gap" "6px" ] [ + label [Html.Attributes.for "select_consulate"] [ Html.text "Choose Consulate Country: " ] + , select + [ Html.Attributes.id "select_consulate" + , Html.Attributes.name "consulate" + , Html.Attributes.style "width" "10rem" + , onInput handleConsulateChange + ] + (List.map consulate_option consulate_countries) + ] + ] ] , chart dataLines , div [ Html.Attributes.style "display" "flex" diff --git a/src/Views/MapView.elm b/src/Views/MapView.elm index 505371ba51b3a02a4f51814853ea7484277f29a9..c5f823d56dc1afa8248a99075e6c0589859262d2 100644 --- a/src/Views/MapView.elm +++ b/src/Views/MapView.elm @@ -145,8 +145,8 @@ update msg global model = -- Set selected consulate country. Deselect if the same country was clicked again. ConsulateCountry -> if Just country == (model.selectedConsulate |> Maybe.map Tuple.first) - then (global, { model | selectedConsulate = Nothing }, Cmd.none) -- Deselect. - else (global, { model | selectedConsulate = Just ( country, Nothing ) }, Cmd.none) -- Select. + then ( { global | selectedConsulateCountry = Nothing }, { model | selectedConsulate = Nothing }, Cmd.none) -- Deselect. + else ( { global | selectedConsulateCountry = Just country.context.meta.iso_a2 }, { model | selectedConsulate = Just ( country, Nothing ) }, Cmd.none) -- Select. -- Do nothing. _ -> (global, model, Cmd.none)